diff --git a/.github/workflows/oryx-python-build-test.yml b/.github/workflows/oryx-python-build-test.yml index 5f1240c..f1a7b7b 100644 --- a/.github/workflows/oryx-python-build-test.yml +++ b/.github/workflows/oryx-python-build-test.yml @@ -3,7 +3,7 @@ name: Oryx Python 3.11 Build Test on: push: paths: - - 'samples/azure-app-service/oryx-test/**' + - 'tests/fixtures/oryx-python-app/**' - '.github/workflows/oryx-python-build-test.yml' workflow_dispatch: @@ -20,7 +20,7 @@ jobs: - name: Oryx build Python 3.11 run: | docker run --rm \ - -v ${{ github.workspace }}/samples/azure-app-service/oryx-test:/app \ + -v ${{ github.workspace }}/tests/fixtures/oryx-python-app:/app \ mcr.microsoft.com/oryx/build:github-actions-debian-bullseye \ oryx build /app \ --platform python \ @@ -29,4 +29,4 @@ jobs: - name: Verify build output run: | echo "=== Build output ===" - ls -la ./samples/azure-app-service/oryx-test/ + ls -la ./tests/fixtures/oryx-python-app/ diff --git a/.gitignore b/.gitignore index 9b02242..1fbe0ff 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,6 @@ __pycache__/ site/ .worktrees/ .superpowers/ -samples/azure-monitor/source-material/sre-agent-event-lab/evidence/ +docs/services/**/samples/**/evidence/ /project/plans/2026-09-12-public-github-pages.md /project/specs/2026-09-12-public-github-pages-design.md diff --git a/AGENTS.md b/AGENTS.md index 9fd29cf..360152c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,65 +3,94 @@ ## Public documentation contract - 상세 기준은 `docs/contributing/index.md`를 단일 설명 문서로 사용한다. -- 공개 기술 문서는 `docs////index.md` page bundle로 작성한다. -- ``은 `cases`, `guides`, `labs`, `research` 중 하나이며 front matter의 `document_type`과 일치해야 한다. -- Azure 제품의 ``는 `azure-`와 공식 전체 제품명을 kebab-case로 조합한다(예: `azure-kubernetes-service`). -- Microsoft 제품은 공식 명칭을 보존하여 이름이 `Microsoft`로 시작하면 `microsoft-` slug를 사용하고, 오픈소스와 그 밖의 제품은 공식 프로젝트·제품명을 kebab-case로 사용한다. -- 새 서비스, 기술 또는 태그가 필요할 때만 `docs-taxonomy.yml`을 함께 수정한다. -- 개별 문서를 추가하기 위해 `mkdocs.yml`, `docs/.nav.yml`, README 또는 수동 문서 목록을 수정하지 않는다. 빌드가 폴더와 메타데이터에서 메뉴·색인을 생성한다. +- 공개 기술 문서는 + `docs/services//[/]/index.md` topic package로 + 작성한다. +- topic entry는 `/index.md`이며 `topic_order`를 쓰지 않는다. 연결된 + 자식 문서는 `//index.md`에 두고 `topic_order`를 1부터 + 중복 없이 연속으로 지정한다. +- `document_type`은 `case`, `guide`, `lab`, `research` 중 하나이며 + collection 호환 색인과 수명주기 규칙을 선택한다. 물리 경로에는 + collection 이름을 사용하지 않는다. +- Azure 제품의 ``는 `azure-`와 공식 전체 제품명을 kebab-case로 + 조합한다. 이름이 `Microsoft`로 시작하는 제품은 `microsoft-` slug를 + 사용하고, 그 밖의 제품은 공식 프로젝트·제품명을 kebab-case로 사용한다. +- 새 서비스, 기술 또는 태그가 필요할 때만 `docs-taxonomy.yml`을 함께 + 수정한다. +- 개별 문서를 추가하기 위해 `mkdocs.yml`, `docs/.nav.yml`, README 또는 + 수동 문서 목록을 수정하지 않는다. + +## Topic samples and redirects + +- 실행 가능한 프로젝트, 배포 매니페스트와 대용량 결과물은 + `docs/services///samples//`에 둔다. +- 각 sample에는 `sample.yml`과 `README.md`가 필요하다. manifest의 + `kind`는 `runnable` 또는 `artifact`이며, `used_by`는 `index` 또는 직접 + 자식 slug를 중복 없이 지정한다. +- 최상위 `samples/`에는 공개 콘텐츠를 추가하지 않는다. +- 페이지에서 렌더링할 이미지만 같은 bundle의 `images/`에 두며 의미 있는 + 대체 텍스트를 작성한다. +- 이전 공개 URL은 canonical 문서의 `redirect_from`으로 보존한다. redirect + 경로에 원본 Markdown을 다시 만들지 않는다. ## Publishing automation -- 문서의 원본은 Markdown page bundle과 front matter다. 기존 분류의 새 글은 `docs/guides/azure-kubernetes-service//index.md` 같은 bundle만 추가하고, 메뉴에 문서 경로를 수동 등록하지 않는다. -- 메뉴 트리, 서비스별·태그별·전체 글 목록과 검색 인덱스, 기존 문서 유형별 색인은 빌드 시 자동 생성한다. 기존 원문·서비스·유형별 색인 URL은 보존한다. 브라우저에서 Markdown을 다시 읽거나 별도의 수동 문서 목록을 동기화하는 구조를 추가하지 않는다. -- 서비스별·태그별 중메뉴 페이지와 `전체 글` 페이지의 본문·카드·링크·문서 수 역시 생성 결과다. 개별 글을 추가·수정할 때 이 페이지나 메뉴의 콘텐츠를 수동으로 수정하지 않는다. -- `mkdocs.yml`과 `docs/.nav.yml`은 사이트 공통 설정과 대메뉴 구성에만 사용한다. 새 서비스·기술·태그를 처음 도입할 때의 공통 분류 등록은 `docs-taxonomy.yml`에서 처리한다. -- 홈의 대표 글은 문서의 `featured: true`로 지정한다. 문서·서비스·사용된 태그 수와 서비스·태그·전체 글 탐색 카드도 자동 생성한다. 생성기에 특정 문서·서비스·태그 경로나 제목 목록을 하드코딩하지 않는다. -- 게시 자동화를 바꿀 때는 기존 설정·taxonomy·홈·문서를 그대로 두고 기존 분류의 Markdown bundle 하나만 추가해 재빌드하는 통합 테스트로 검증한다. 메뉴·서비스별/태그별 중메뉴 페이지·전체 글·홈 통계·검색이 자동 갱신되는지 확인하며, 설정 파일의 문자열이 존재하는지만 검사하는 테스트로 대체하지 않는다. - -## Reader interface - -- 공개 사이트 이름은 `Azure Engineering Notes`, 부제는 `Azure 기반 시스템의 설계·구현·운영 기록`이다. 저장소 이름과 URL의 `hellices/devguidesample`은 유지한다. -- 독자는 개발자다. 제목과 설명은 구체적인 증상, 기술적 선택, 관측과 판단의 근거를 드러내고 일반적인 홍보 문구나 관료적인 표현을 피한다. -- 대메뉴는 `홈`, `서비스별 보기`, `태그별 보기`, `전체 글`, `기여하기`로 구성한다. 사이드바의 주요 글 경로는 서비스별 보기 → 서비스 → 문서다. 문서 이름은 front matter의 `title`, 서비스 이름은 taxonomy를 사용하고 bundle 폴더를 불필요한 추가 단계로 노출하지 않는다. -- 서비스 목록은 front matter의 `services` 전체를 반영한다. 사이드바에는 bundle 폴더의 대표 서비스 아래 원본 글을 한 번만 배치하며 모든 수명주기 유형을 함께 보여준다. `cases`·`guides`·`labs`·`research`는 내부 작성·수명주기 분류로 유지하되 유형별 대메뉴나 독자 목록·대표 글의 유형 배지·색상 구분에 사용하지 않는다. -- 태그별 보기 → 해당 태그의 글 목록 → 원문으로 연결한다. 문서 상단과 카드의 태그는 검색이 아니라 동일한 태그별 목록으로 연결하고, 해당 태그가 front matter에 정확히 지정된 글만 보여준다. 사용된 태그와 글 수는 자동 생성하며 전체 글 목록에는 각 원본 글을 한 번만 표시한다. -- 그룹은 접기·펼치기가 가능하고 현재 문서의 경로만 기본으로 열린다. `navigation.sections`나 `navigation.expand`로 모든 그룹을 강제로 펼치지 않는다. -- 큰 화면에서는 전체 문서 사이드바를, 작은 화면에서는 햄버거 메뉴를 제공한다. 들여쓰기·굵기·구분선으로 계층을 표시하고 현재 문서를 강조한다. -- 글자 크기는 GitHub Markdown 수준을 기준으로 한다: 본문 16px, H1 32px, H2 24px, H3 20px, 코드 약 13.6px. 홈 제목만 과도하게 키우지 않으며 밝은·어두운 테마와 모바일에서도 실제 크기·가독성을 확인한다. -- 검토 상태와 확인일은 사실대로 메타데이터에 유지하되, 마이그레이션·검토 미완료 같은 관리용 안내를 본문 배너나 목록 배지로 자동 노출하지 않는다. 출처 링크와 실제 기술적 제약·비용 경고는 보존한다. +- 메뉴 트리, 서비스별·태그별·전체 글 목록, 검색 인덱스, collection 호환 + 색인과 이전 URL redirect page는 빌드 시 자동 생성한다. +- 대메뉴는 `홈`, `서비스별 보기`, `태그별 보기`, `전체 글`, `기여하기`다. + 서비스 branch 아래에는 canonical topic entry와 순서가 있는 자식 문서만 + 배치한다. redirect page와 collection 호환 색인은 이 branch에 넣지 않는다. +- 서비스 목록은 front matter의 `services` 전체를 반영한다. 사이드바에는 + 경로의 대표 서비스 아래 원본 topic을 한 번만 배치한다. +- 태그 링크는 정확히 일치하는 태그별 목록으로 연결한다. 전체 글에는 각 + canonical 문서를 한 번만 표시한다. +- 홈 대표 글은 topic entry의 `featured: true`로 지정한다. 생성기에 특정 + 문서·서비스·태그 경로나 제목 목록을 하드코딩하지 않는다. +- sample 파일은 public-safety 검사 대상이지만 MkDocs 사이트와 검색 + 인덱스에는 포함하지 않는다. ## Content lifecycle -- 발생 시점의 환경, 관측값, 조사와 해결 이력은 `cases`에 보존한다. -- 현재 권장 절차, 지원 상태와 버전별 차이는 `guides`에서 계속 갱신한다. -- 당시 증거를 보존하는 사례와 현재 절차를 갱신하는 가이드의 수명주기가 다르면 문서를 분리하고 `related_cases`·`related_guides`로 연결한다. `guides`·`research`의 연관된 비교·분석과 적용 절차는 한 문서에 함께 담을 수 있다. -- 실행 가능한 프로젝트, 배포 매니페스트와 대용량 결과물은 `samples/`에 두고 문서에서는 GitHub 소스 링크를 사용한다. -- 페이지에서 렌더링할 이미지만 같은 bundle의 `images/`에 두며 의미 있는 대체 텍스트를 작성한다. +- 발생 시점의 환경, 관측값, 조사와 해결 이력은 `document_type: case`로 + 보존한다. +- 현재 권장 절차, 지원 상태와 버전별 차이는 `document_type: guide`에서 + 계속 갱신한다. +- 재현 가능한 실습은 `document_type: lab`, 비교·벤치마크·아키텍처 조사는 + `document_type: research`를 사용한다. +- 당시 증거를 보존하는 사례와 현재 절차를 갱신하는 가이드의 수명주기가 + 다르면 문서를 분리하고 `related_cases`·`related_guides`로 연결한다. ## Official-source verification -- Microsoft 또는 Azure의 동작, 지원 여부, 제한, 버전, 구성 단계, CLI/API 사용법을 추가하거나 바꿀 때 `.github/skills/verify-with-microsoft-learn/SKILL.md`의 `verify-with-microsoft-learn` skill을 반드시 사용한다. -- Microsoft Learn MCP 검색 뒤 선택한 원문 전체를 조회하고, 주장과 적용 범위를 비교한다. -- `official_sources`, `sources_checked_at`, `verification_status`는 실제 검증 결과와 일치시킨다. URL만 추가한 것은 검증이 아니다. -- Microsoft Learn이 최종 권위가 아닌 Kubernetes·CNCF 세부 동작은 upstream 공식 문서도 함께 확인한다. -- 의미 검증을 완료하지 못하면 `verification_status: needs-review`로 남기고 완료했다고 표현하지 않는다. +- Microsoft 또는 Azure의 동작, 지원 여부, 제한, 버전, 구성 단계, + CLI/API 사용법을 추가하거나 바꿀 때 repository skill + `verify-with-microsoft-learn`을 반드시 사용한다. +- Microsoft Learn MCP 검색 뒤 선택한 원문 전체를 조회하고 주장과 적용 + 범위를 비교한다. +- `official_sources`, `sources_checked_at`, `verification_status`는 실제 + 검증 결과와 일치시킨다. URL만 추가한 것은 검증이 아니다. +- Microsoft Learn이 최종 권위가 아닌 Kubernetes·CNCF 세부 동작은 + upstream 공식 문서도 함께 확인한다. +- 의미 검증을 완료하지 못하면 `verification_status: needs-review`로 + 남기고 완료했다고 표현하지 않는다. ## Public safety -- 고객·조직·사용자 실명, 구독·테넌트 ID, 비밀, 연결 문자열, 내부 URL·IP·호스트명, 개인정보가 담긴 로그와 승인되지 않은 화면을 공개 문서에 넣지 않는다. -- 실제 식별자는 문서 전체에서 일관된 가상 값으로 치환한다. -- `.azure/`, `.claude/`, `.DS_Store`, `sim-env.json`과 실습 실행으로 생성된 `evidence/`는 로컬 상태이며 커밋하지 않는다. 이미 추적 중인 파일은 ignore 규칙만 추가해도 제외되지 않으므로 로컬 파일을 보존하면서 Git 추적도 해제한다. -- 공유 개발 설정(`.vscode/mcp.json`, `.devcontainer/`)과 예제 입력(`.env.example`, 배포 매개변수), 공개용으로 정리한 샘플 자료는 로컬 상태와 구분해 유지한다. -- 완료된 일회성 구축 계획·설계 기록은 현재 작성 지침과 구분하고 세션 또는 로컬 보관소에 남긴다. 공개 문서나 샘플에서 참조하는 자료·다이어그램 원본은 생성 파일 또는 중복이라는 이유만으로 삭제하지 않는다. +- 고객·조직·사용자 실명, 구독·테넌트 ID, 비밀, 연결 문자열, 내부 + URL·IP·호스트명, 개인정보가 담긴 로그와 승인되지 않은 화면을 공개 + 문서나 sample에 넣지 않는다. +- 실제 식별자는 문서와 sample 전체에서 일관된 가상 값으로 치환한다. +- `.azure/`, `.claude/`, `.DS_Store`, `sim-env.json`과 실행 중 생성된 + `evidence/`는 로컬 상태이며 커밋하지 않는다. +- 공유 개발 설정, 예제 입력과 비식별화한 공개 sample은 로컬 상태와 + 구분해 유지한다. ## Required validation -문서 CI는 `tests/docs`와 공개 문서·사이트 검증을 대상으로 한다. SRE 실습 등 `samples/`의 애플리케이션 테스트나 의존성을 문서 CI에 묶지 않는다. 게시 스크립트나 사이트 동작을 변경하면 `python -m pytest tests/docs -q`도 실행한다. - 문서 또는 사이트 구성을 변경한 뒤 저장소 루트에서 실행한다. ```powershell +python -m pytest tests/docs -q python scripts/docs/validate_metadata.py python scripts/docs/validate_sources.py python scripts/docs/validate_links.py @@ -70,4 +99,6 @@ mkdocs build --strict python scripts/docs/validate_search_index.py ``` -테스트나 검증 실패를 무시하거나 생성된 `site/` 및 검색 인덱스를 커밋하지 않는다. +테스트나 검증 실패를 무시하거나 생성된 `site/` 및 검색 인덱스를 커밋하지 +않는다. sample 애플리케이션의 별도 테스트나 의존성은 해당 sample 변경 +범위에서만 실행한다. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3ccb13..c431edd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,14 +1,27 @@ # 기여하기 -새 공개 문서는 `docs////index.md`에 추가합니다. 올바른 폴더와 front matter를 사용하면 메뉴와 색인은 빌드 시 자동 생성됩니다. +새 공개 문서는 +`docs/services//[/]/index.md`에 추가합니다. +`document_type`은 `case`, `guide`, `lab`, `research` 중 문서의 수명주기에 +맞게 선택하지만 물리 폴더에는 사용하지 않습니다. -문서 유형 선택, 메타데이터, 본문 구조, 태그, 이미지와 공개 안전성의 기준 문서는 [상세 기여 지침](docs/contributing/index.md)입니다. +실행 코드, 배포 매니페스트와 결과물은 +`docs/services///samples//`에 `sample.yml`과 +`README.md`를 포함해 둡니다. 최상위 collection 폴더와 `samples/`에는 새 +공개 콘텐츠를 추가하지 않습니다. -Microsoft 또는 Azure 기술 내용을 새로 쓰거나 바꿀 때는 repository skill `verify-with-microsoft-learn`을 사용해 Microsoft Learn 원문과 대조하고 출처 메타데이터를 갱신해야 합니다. +문서 유형, topic/child 순서, redirect, sample manifest, 메타데이터, 이미지, +공개 안전성의 단일 기준은 +[상세 기여 지침](docs/contributing/index.md)입니다. + +Microsoft 또는 Azure 기술 내용을 새로 쓰거나 바꿀 때는 repository skill +`verify-with-microsoft-learn`을 사용해 Microsoft Learn 원문과 대조하고 +출처 메타데이터를 갱신해야 합니다. 제출 전 다음을 실행하세요. ```powershell +python -m pytest tests/docs -q python scripts/docs/validate_metadata.py python scripts/docs/validate_sources.py python scripts/docs/validate_links.py diff --git a/README.md b/README.md index fa76600..0d4a320 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,11 @@ Azure 기반 시스템의 설계·구현·운영 기록을 공유합니다. 실 전체 문서 목록은 파일로 중복 관리하지 않습니다. 올바른 폴더와 front matter를 사용하면 Pages 메뉴, 서비스 색인과 태그 색인에 자동으로 포함됩니다. +공개 문서의 canonical 원본은 +`docs/services//[/]/index.md` topic package입니다. +실행 코드와 결과물은 해당 topic의 `samples//`에 두며, 최상위 +collection 폴더나 `samples/`에는 새 공개 콘텐츠를 추가하지 않습니다. + ## 로컬 미리보기 ```powershell @@ -25,6 +30,7 @@ python -m venv .venv .\.venv\Scripts\mkdocs serve ``` -자세한 작성·검증 규칙은 [CONTRIBUTING.md](CONTRIBUTING.md)를 확인하세요. +자세한 작성·검증 규칙은 +[공개 문서 작성 계약](docs/contributing/index.md)을 확인하세요. > 이 저장소와 Pages는 공개되어 있습니다. 고객·사용자 식별자, 구독·테넌트 ID, 비밀, 내부 URL 또는 승인되지 않은 화면 캡처를 커밋하지 마세요. diff --git a/docs/.nav.yml b/docs/.nav.yml index 23b6605..b13498d 100644 --- a/docs/.nav.yml +++ b/docs/.nav.yml @@ -1,3 +1,4 @@ +# Root reader destinations only; service and topic branches are generated. nav: - 홈: index.md - 서비스별 보기: services diff --git a/docs/assets/stylesheets/extra.css b/docs/assets/stylesheets/extra.css index 687d368..9217680 100644 --- a/docs/assets/stylesheets/extra.css +++ b/docs/assets/stylesheets/extra.css @@ -468,6 +468,10 @@ body { transition: border-color 160ms ease, transform 160ms ease; } +.dg-topic-card { + border-top: 3px solid var(--dg-card-accent); +} + .dg-doc-card:hover, .dg-collection-card:hover, .dg-service-card:hover, @@ -579,7 +583,8 @@ body { background: var(--dg-accent-soft); } -.dg-doc-card .dg-tag { +.dg-doc-card .dg-tag, +.dg-doc-card .dg-topic-child-link { position: relative; z-index: 1; } @@ -746,6 +751,133 @@ body { padding-block: 0.25rem; } +.dg-topic-overview, +.dg-topic-context, +.dg-topic-nav { + background: var(--dg-surface); + border: 1px solid var(--dg-border); + border-radius: 0.5rem; +} + +.dg-topic-overview { + margin-block: 1.25rem 1.75rem; + padding: 1rem 1.1rem; +} + +.md-typeset .dg-topic-overview > p:not(.dg-eyebrow) { + color: var(--dg-muted); + font-size: 0.7rem; + margin: 0; +} + +.dg-topic-list { + display: grid; + gap: 0.35rem; + margin: 0.8rem 0 0; + padding-left: 1.4rem; +} + +.dg-topic-list li { + padding-left: 0.2rem; +} + +.dg-topic-context, +.dg-topic-nav { + display: grid; + grid-template-columns: minmax(0, 1fr) auto auto; + align-items: center; + gap: 0.65rem; + padding: 0.7rem 0.85rem; +} + +.dg-topic-context { + grid-template-columns: minmax(0, 1fr) auto; + margin-bottom: 1.4rem; +} + +.dg-topic-nav { + margin-top: 2rem; +} + +.dg-topic-context span, +.dg-topic-nav span { + color: var(--dg-muted); + font-size: 0.68rem; + line-height: 1.5; +} + +.md-typeset :is(.dg-topic-context, .dg-topic-nav) a { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 1.9rem; + padding: 0.3rem 0.65rem; + background: var(--dg-soft); + border: 1px solid var(--dg-border); + border-radius: 0.3rem; + font-size: 0.65rem; + font-weight: 600; +} + +.md-typeset :is(.dg-topic-context, .dg-topic-nav) a:hover { + background: var(--dg-accent-soft); + border-color: var(--dg-accent); +} + +.dg-related-samples { + border-top: 1px solid var(--dg-border); + margin-top: 2rem; + padding-top: 1.2rem; +} + +.dg-sample-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.8rem; +} + +.dg-sample-card { + position: relative; + display: flex; + flex-direction: column; + min-width: 0; + padding: 0.9rem; + background: var(--dg-surface); + border: 1px solid var(--dg-border); + border-radius: 0.5rem; + transition: border-color 160ms ease, transform 160ms ease; +} + +.dg-sample-card:hover { + border-color: var(--dg-accent); + transform: translateY(-2px); +} + +.md-typeset .dg-sample-card h3 { + margin: 0 0 0.55rem; + font-size: var(--dg-font-h3); +} + +.md-typeset .dg-sample-card h3 a { + color: var(--dg-text); +} + +.md-typeset .dg-sample-card h3 a::after { + content: ""; + position: absolute; + inset: 0; + border-radius: 0.5rem; +} + +.md-typeset .dg-sample-card .dg-card-count { + margin-top: auto; +} + +.md-typeset :is(.dg-topic-list, .dg-topic-context, .dg-topic-nav, .dg-sample-card) a:focus-visible { + outline: 2px solid var(--dg-accent); + outline-offset: 3px; +} + .md-footer, .md-footer-meta { background: var(--dg-bg); @@ -846,6 +978,20 @@ body { padding-top: 1rem; } + .dg-topic-context, + .dg-topic-nav { + grid-template-columns: minmax(0, 1fr); + align-items: stretch; + } + + .md-typeset :is(.dg-topic-context, .dg-topic-nav) a { + width: 100%; + } + + .dg-sample-grid { + grid-template-columns: minmax(0, 1fr); + } + } @media (max-width: 480px) { @@ -875,6 +1021,7 @@ body { .dg-service-card, .dg-browse-card, .dg-tag-card, + .dg-sample-card, .md-typeset .dg-button { transition: none; } @@ -886,4 +1033,8 @@ body { .dg-tag-card:hover { transform: none; } + + .dg-sample-card:hover { + transform: none; + } } diff --git a/docs/contributing/index.md b/docs/contributing/index.md index 654cc47..da02c8e 100644 --- a/docs/contributing/index.md +++ b/docs/contributing/index.md @@ -1,74 +1,166 @@ --- title: 기여하기 -description: 재현할 수 있는 환경과 코드, 판단의 근거를 갖춘 기술 기록을 기여하는 방법 +description: 서비스 중심 topic package와 샘플, 메타데이터, 검증 규칙 ---

CONTRIBUTING

-# 코드와 근거를 함께 남겨주세요 +# 공개 문서 작성 계약 -어떤 환경에서 무엇을 관측했고, 왜 그 방법을 선택했는지까지 적어주세요. 다음 사람이 같은 조건을 재현하고, 자신의 환경에도 적용할 수 있는지 판단하는 데 도움이 됩니다. +이 페이지는 공개 문서 작성 규칙의 단일 기준입니다. 새 문서와 샘플은 모두 +`docs/services/` 아래의 topic package에 둡니다. `cases`, `guides`, `labs`, +`research`는 물리 폴더가 아니라 front matter의 `document_type`과 수명주기 +정책으로만 유지합니다. -
+루트 `README.md`, `AGENTS.md`, `CONTRIBUTING.md`는 이 계약의 요약입니다. -1. **글의 성격을 고릅니다.** 특정 시점의 문제 해결, 재사용할 절차, 직접 해보는 실습, 선택지를 비교한 분석 중 어디에 해당하는지 정합니다. -2. **환경과 근거를 남깁니다.** 코드·관측값·공식 출처를 정리하고, 확인하지 못한 부분은 구분합니다. -3. **공개 전에 검증합니다.** 링크와 빌드 결과를 확인하고 실제 환경의 식별자나 비밀이 남아 있지 않은지 살펴봅니다. +## 문서 유형과 수명주기 -
+| `document_type` | 용도 | 유지 방식 | +|---|---|---| +| `case` | 특정 시점의 증상, 관측, 조사, 원인과 해결 결과 | 당시 사실을 보존하고 필요하면 `historical`로 표시 | +| `guide` | 현재 권장하는 진단·구성 절차 | 지원 상태와 버전 변화에 맞춰 같은 문서를 갱신 | +| `lab` | 배포, 실행, 검증, 정리를 재현하는 실습 | 비용·검증·정리 절차까지 재검증 | +| `research` | 선택지, 성능, 아키텍처 비교 | 조사 기준일, 범위와 한계를 보존 | -이 페이지가 공개 문서 작성 규칙의 기준입니다. 루트 `CONTRIBUTING.md`와 `AGENTS.md`는 핵심 규칙과 실행할 검증 명령을 안내합니다. +당시 증거를 보존할 사례와 계속 갱신할 현재 절차는 분리하고 +`related_cases`·`related_guides`로 연결합니다. 비교와 적용 절차가 같은 +수명주기라면 하나의 `guide` 또는 `research` 안에 함께 둘 수 있습니다. -## 문서 유형 선택 +## canonical topic package -| 질문 | 모음 | 수명주기 | -|---|---|---| -| 특정 시점에 무엇이 발생했고 어떻게 해결했나? | 트러블슈팅 (`cases`) | 당시 사실을 보존하고 필요하면 `historical`로 표시 | -| 지금 같은 문제를 어떻게 진단·구성해야 하나? | 구현 가이드 (`guides`) | 제품 변화에 맞춰 같은 문서를 갱신 | -| 독자가 리소스를 배포해 재현할 수 있나? | 실습 (`labs`) | 비용, 검증, 정리 절차까지 재검증 | -| 선택지·성능·아키텍처를 어떤 기준으로 비교했나? | 비교·분석 (`research`) | 조사 기준일과 한계를 보존 | +### 독립 topic -문서 유형은 폴더·메타데이터와 보존·갱신 정책을 위한 내부 분류입니다. 독자 메뉴와 카드에는 유형을 표시하지 않고, 같은 서비스의 글을 유형 구분 없이 함께 보여줍니다. +자식 문서가 없는 글은 다음 구조를 사용합니다. -`guides`와 `research`에는 서로 연결된 비교·분석과 적용 절차를 함께 담을 수 있습니다. 비교와 구현이 함께 있다는 이유만으로 글을 나누지 않고, 문서의 주된 목적에 맞춰 유형을 선택합니다. +```text +docs/services/// +├── index.md +└── images/ + └── +``` -당시의 증거를 보존할 장애 이력과 계속 갱신할 현재 절차처럼 수명주기가 다르면 두 문서로 분리합니다. 사례에는 당시 환경·관측·결과를, 가이드에는 현재 지원 범위·권장 절차·검증·롤백을 두고 `related_cases`와 `related_guides`로 연결합니다. +`images/`는 페이지에서 직접 렌더링하는 이미지가 있을 때만 만듭니다. -## 폴더만으로 자동 게시하기 +### 연결된 topic + +순서가 있는 여러 문서는 하나의 topic package로 묶습니다. ```text -docs//// +docs/services/// ├── index.md -└── images/ +├── / +│ └── index.md +├── / +│ └── index.md +├── images/ +└── samples/ ``` -URL에 쓰이는 폴더 이름은 소문자 kebab-case 영문으로 작성합니다. Azure 제품의 ``는 `azure-`와 공식 전체 제품명을 조합합니다(예: `azure-kubernetes-service`, `azure-database-for-mysql`). Microsoft 제품은 공식 명칭을 보존하므로 이름이 `Microsoft`로 시작하면 `microsoft-` slug를 사용합니다(예: `microsoft-foundry`). 오픈소스와 그 밖의 제품은 임의의 공급자 접두사를 붙이지 않고 공식 프로젝트·제품명을 사용합니다. +루트 `index.md`가 topic entry입니다. entry에는 `topic_order`를 쓰지 않습니다. +직접 자식 문서에는 `topic_order: 1`, `topic_order: 2`처럼 1부터 시작하는 +중복 없는 연속 번호를 지정합니다. 손자 문서 구조는 허용하지 않습니다. +`index`는 topic entry를 가리키는 예약어이므로 자식 폴더 slug로 사용할 수 +없습니다. -문서를 위 경로에 넣고 유효한 front matter를 작성하면 다음 빌드에서 자동으로 Pages에 포함됩니다. +### topic 소유 sample -- Awesome Nav의 glob fallback이 원본 페이지를 발견하고, 빌드 훅이 URL을 바꾸지 않은 채 서비스별 메뉴에 배치합니다. -- 생성기가 서비스별·태그별·전체 글 목록과 기존 문서 유형별 색인을 갱신합니다. -- 생성기와 훅이 문서·카드의 태그 링크와 태그별 목록을 만듭니다. Material tags는 메타데이터 처리를 유지하되 기본 태그·목록 UI는 표시하지 않습니다. -- Material search가 한국어와 영어 제목·본문·섹션을 전체 텍스트로 색인합니다. +실행 코드, 배포 매니페스트, 재현 프로젝트와 대용량 결과물은 이를 사용하는 +topic 안에 둡니다. -대메뉴는 **홈 · 서비스별 보기 · 태그별 보기 · 전체 글 · 기여하기**입니다. 사이드바의 주요 글 경로는 **서비스별 보기 → 서비스 → 문서**이며, 모든 수명주기 유형의 글을 함께 배치합니다. 문서 이름은 front matter의 `title`, 서비스 이름은 taxonomy를 사용합니다. bundle 폴더를 별도 메뉴 단계로 노출하지 않고, 각 그룹은 접고 펼칠 수 있으며 현재 읽는 문서의 경로만 기본으로 열립니다. 큰 화면에서는 전체 사이드바를, 작은 화면에서는 햄버거 메뉴를 제공합니다. 메뉴 데이터는 빌드 결과에 포함되므로 브라우저가 Markdown을 다시 읽어 구성하지 않습니다. +```text +docs/services///samples// +├── sample.yml +├── README.md +└── +``` -서비스 목록은 front matter의 `services`에 지정한 모든 서비스를 반영합니다. 여러 서비스와 관련된 글은 각 서비스 목록에서 찾을 수 있지만, 사이드바에는 bundle 경로의 ``를 대표 서비스로 삼아 원본 글을 한 번만 배치합니다. 서비스 제목은 유형을 섞은 해당 서비스의 글 목록으로 연결되고, 하위 메뉴의 문서 링크는 기존 원문 URL을 그대로 사용합니다. +`sample.yml` 형식은 다음과 같습니다. -태그 탐색은 **태그별 보기 → 해당 태그의 글 목록 → 원문** 순서입니다. 문서 상단과 카드의 태그도 검색 화면이 아니라 같은 태그별 목록으로 연결합니다. 목록에는 해당 태그가 front matter의 `tags`에 정확히 지정된 글만 포함합니다. 태그 개요에는 실제로 사용된 태그와 각 태그의 실제 글 수를 자동으로 표시하며, `전체 글`에는 각 원본 글을 한 번만 표시합니다. +```yaml +title: Event lab +description: Reproduces the monitored incident. +kind: runnable +used_by: + - index + - setup +``` + +- `title`, `description`, `kind`는 비어 있지 않은 문자열입니다. +- `kind`는 `runnable` 또는 `artifact`입니다. +- `used_by`는 sample을 표시할 문서 slug의 중복 없는 목록입니다. +- topic entry는 `index`, 자식 문서는 폴더 slug로 지정합니다. +- 각 sample에는 사용·검증·정리 방법을 설명하는 `README.md`가 필요합니다. +- sample이 있는 topic에는 entry `index.md`가 반드시 있어야 합니다. + 자식 문서 아래에 `samples/`를 두거나 topic의 `samples/` 바로 아래에 + 파일을 두지 않습니다. sample package 내부의 하위 `samples/` 폴더는 + 해당 package의 payload로 취급합니다. +- sample 파일은 공개 안전성 검사를 받지만 MkDocs 사이트와 검색 색인에는 + 복사되지 않습니다. 문서에서는 생성되는 sample card 또는 GitHub 소스 + 링크로 연결합니다. + +최상위 `samples/`에는 공개 콘텐츠를 추가하지 않습니다. -`services/index.md`·`services//index.md`, `tags/index.md`·`tags/.md`, `articles/index.md`는 빌드 시 생성됩니다. 기존 유형별 색인과 `docs///index.md`에 해당하는 서비스 색인, 기존 원문·서비스 URL도 호환성을 위해 유지하지만 유형별 대메뉴는 만들지 않습니다. 작성자는 이 색인 파일이나 별도의 `docs/tags.md`를 직접 추가하지 않습니다. +## 경로와 redirect 규칙 -개별 문서 추가를 위해 `mkdocs.yml`, `docs/.nav.yml`, README 또는 수동 목록을 수정하지 않습니다. 새 서비스·기술·태그 식별자가 필요할 때만 `docs-taxonomy.yml`을 같은 PR에서 한 번 갱신합니다. +모든 ``, ``, ``, `` slug는 소문자 +kebab-case를 사용합니다. Azure 제품의 ``는 `azure-`와 공식 전체 +제품명을 조합합니다. 이름이 `Microsoft`로 시작하는 제품은 `microsoft-` +slug를 사용하고, 그 밖의 제품은 공식 프로젝트·제품명을 kebab-case로 +표현합니다. -| 추가하거나 바꾸려는 것 | 작성자가 수정할 곳 | -|---|---| -| 기존 서비스·태그를 사용하는 새 글 | 해당 bundle의 `index.md` | -| 글에서 보여줄 이미지 | 해당 bundle의 `images/` | -| 처음 사용하는 서비스·기술·태그 | 새 글과 `docs-taxonomy.yml` | -| 메뉴·목록·검색에 새 글 노출 | 별도 수정 없음 — 빌드가 처리 | +이전 공개 URL을 보존해야 하면 canonical 문서 front matter에 추가합니다. + +```yaml +redirect_from: + - guides///index.md +``` -홈에 소개할 글은 해당 문서의 front matter에 `featured: true`를 지정합니다. 홈의 대표 글은 이 메타데이터에서 최대 세 편을 고르며, 지정한 글이 없으면 문서 유형별로 글을 골라 보여줍니다. 이때 문서 유형은 내부 선택 기준이며, 독자 메뉴나 카드에서 유형을 구분해 표시하지 않습니다. 빌드가 문서·서비스·사용된 태그 수와 서비스·태그·전체 글 탐색 카드도 자동으로 생성합니다. 홈이나 생성기에 특정 문서·서비스·태그 목록을 하드코딩하지 않습니다. +redirect 경로는 `cases`, `guides`, `labs`, `research` 중 하나로 시작하는 +기존 4단계 `index.md` 경로여야 합니다. 해당 경로에 원본 Markdown을 다시 +만들지 않습니다. 빌드가 redirect page를 생성하며 검색과 서비스/topic +탐색에서는 제외합니다. +이전 collection/service 색인 주소도 유지하며, 현재 소속과 이전 소속이 +겹치면 canonical 문서를 중복 없이 합칩니다. 이 호환 색인에서는 topic +카드와 함께 해당 자식 문서로 바로 가는 링크도 제공합니다. + +`document_type`은 collection 호환 색인과 수명주기 요구사항을 선택하지만 +물리 경로를 결정하지 않습니다. `services/`, `tags/`, `articles/`와 +collection 호환 색인은 빌드가 생성합니다. 개별 문서를 추가하기 위해 +`mkdocs.yml`, `docs/.nav.yml`, README 또는 수동 문서 목록을 수정하지 +않습니다. + +## 세 가지 작성 workflow + +### 1. 독립 topic 추가 + +1. `docs/services///index.md`를 만듭니다. +2. 공통 front matter와 선택한 `document_type`의 필수 필드를 채웁니다. +3. 렌더링 이미지가 있으면 같은 bundle의 `images/`에 두고 의미 있는 대체 + 텍스트를 작성합니다. +4. 새 taxonomy 값이 있을 때만 `docs-taxonomy.yml`을 수정합니다. +5. 필수 검증을 모두 실행합니다. + +### 2. 연결된 topic 추가 또는 확장 + +1. topic entry를 `docs/services///index.md`에 둡니다. +2. 직접 자식마다 `/index.md`를 만들고 연속 `topic_order`를 + 지정합니다. +3. 이전 URL이 있으면 각 canonical 문서에 고유한 `redirect_from`을 + 기록합니다. +4. topic 안의 링크는 canonical `.md` 상대 경로로 작성합니다. +5. entry 목차, 이전·다음 이동, 서비스 메뉴가 자동 생성되는지 strict build로 + 확인합니다. + +### 3. sample 추가 + +1. `docs/services///samples//`을 만듭니다. +2. `sample.yml`과 `README.md`를 먼저 작성하고 `used_by` 소유 문서를 + 지정합니다. +3. 코드·매니페스트·결과물을 같은 sample 폴더에 둡니다. +4. 실제 식별자와 비밀을 가상 값으로 치환합니다. +5. public-safety 검사가 sample을 검사하고 strict build의 `site/`에는 sample + 파일이 없는지 확인합니다. ## 공통 front matter @@ -92,41 +184,44 @@ applies_to: [AKS 1.34+] --- ``` -`services`, `technologies`, `tags`와 유형별 `status`는 `docs-taxonomy.yml`의 값을 사용합니다. 제목은 한국어여도 제품명, API, 명령과 오류 메시지는 공식 영문 표기를 함께 써 검색 가능하게 합니다. - -유형별 전체 형식은 [사례 템플릿](case-template.md), [가이드 템플릿](guide-template.md), [실습 템플릿](lab-template.md), [리서치 템플릿](research-template.md)을 사용합니다. - -## Microsoft Learn MCP로 기술 주장 검증하기 +`services`, `technologies`, `tags`와 유형별 `status`는 +`docs-taxonomy.yml`의 값을 사용합니다. 유형별 전체 형식은 +[사례 템플릿](case-template.md), [가이드 템플릿](guide-template.md), +[실습 템플릿](lab-template.md), [리서치 템플릿](research-template.md)을 +사용합니다. 홈에 소개할 topic entry에는 `featured: true`를 지정할 수 +있습니다. -워크스페이스의 `.vscode/mcp.json`은 공식 Microsoft Learn MCP 서버를 연결합니다. Microsoft 또는 Azure의 제품 동작, 지원 상태, 제한, 버전, 구성 단계나 CLI/API 사용법을 작성할 때 repository skill `verify-with-microsoft-learn`을 사용합니다. +## 공식 출처 검증 -1. 변경한 문장에서 검증 가능한 주장을 나눕니다. -2. MCP가 현재 제공하는 도구 설명을 조회하고 Microsoft Learn을 검색합니다. -3. 검색 요약으로 끝내지 않고 관련 원문 전체를 가져옵니다. -4. 적용 범위, 전제 조건, 버전, 지원 상태와 제한을 본문의 주장과 비교합니다. -5. 실제 사용한 원문의 제목과 정규 URL을 `official_sources`에 기록하고 `sources_checked_at`을 확인일로 갱신합니다. -6. 모든 중요한 주장이 맞을 때만 `verification_status: verified`로 표시합니다. +Microsoft 또는 Azure의 동작, 지원 상태, 제한, 버전, 구성 단계나 CLI/API +사용법을 추가하거나 바꿀 때 repository skill `verify-with-microsoft-learn`을 +사용합니다. Microsoft Learn 검색 결과만 인용하지 말고 선택한 원문 전체를 +확인하여 주장, 적용 범위와 전제 조건을 비교합니다. -Kubernetes나 CNCF 프로젝트 구현처럼 Microsoft Learn이 최종 권위가 아닌 세부 사항은 `kubernetes.io`, `cncf.io` 또는 해당 프로젝트 공식 원문도 추가합니다. 검증을 끝내지 못한 문서는 `verification_status: needs-review`로 표시합니다. 자동 CI는 필드와 도메인을 검사하지만 본문의 의미를 대신 판단하지 않습니다. +실제로 확인한 원문의 제목과 정규 URL을 `official_sources`에 기록하고 +`sources_checked_at`을 확인일로 갱신합니다. 중요한 주장을 모두 확인했을 +때만 `verification_status: verified`로 표시합니다. 검증을 완료하지 못하면 +`verification_status: needs-review`로 남깁니다. Kubernetes·CNCF 세부 +동작은 필요한 경우 upstream 공식 문서도 함께 확인합니다. -## 본문과 파일 규칙 +## 본문과 공개 안전성 - 페이지마다 하나의 H1을 사용하고 H2/H3 순서를 건너뛰지 않습니다. -- 코드 블록에는 언어를 지정하고 비밀·실제 식별자를 넣지 않습니다. -- 문서 간 링크는 `.md` 소스 상대 경로를, 페이지 이미지는 `images/` 상대 경로를 사용합니다. -- 모든 이미지에 내용을 설명하는 대체 텍스트를 작성합니다. -- 실행 코드와 배포 파일은 `samples///`에 두고 GitHub 파일 링크로 연결합니다. -- 원시 로그, 캡처 타임라인과 생성 결과는 문서가 아니라 sample/evidence로 보관합니다. - -## 공개 안전성 - -고객·조직·사용자 실명, 구독·테넌트 ID, 키·토큰·인증서·연결 문자열, 내부 URL·IP·호스트명, 개인정보가 포함된 로그와 승인되지 않은 고객 화면을 게시하지 않습니다. `validate_public_safety.py`가 환경 고유 식별자의 대표 패턴을 검사하지만, 자동 검사와 별개로 사람이 원문과 이미지를 확인해야 합니다. +- 문서 링크는 canonical `.md` 상대 경로를 사용합니다. +- 렌더링 이미지는 같은 bundle의 `images/`에 두고 대체 텍스트를 씁니다. +- 고객·조직·사용자 실명, 구독·테넌트 ID, 키·토큰·인증서·연결 문자열, + 내부 URL·IP·호스트명, 개인정보가 포함된 로그와 승인되지 않은 화면을 + 게시하지 않습니다. +- 실제 식별자는 문서와 sample 전체에서 일관된 가상 값으로 치환합니다. +- `.azure/`, `.claude/`, `.DS_Store`, `sim-env.json`, 실행 중 생성된 + `evidence/`는 커밋하지 않습니다. -`.azure/`의 개인별 작업 상태, `.claude/`, `.DS_Store`, `sim-env.json`, 실습 실행으로 생성된 `evidence/`는 커밋하지 않습니다. 공유 개발 설정과 `.env.example` 같은 예제 입력은 유지하고, 공개할 관측 자료는 비식별화한 뒤 문서 또는 샘플 자료로 별도 정리합니다. 이미 Git이 추적 중인 로컬 파일은 `.gitignore`에 추가하는 것만으로 제외되지 않으므로, 파일을 로컬에 보존한 채 추적을 해제해야 합니다. +## 필수 검증 -## 로컬 검증 +저장소 루트에서 다음을 모두 실행합니다. ```powershell +python -m pytest tests/docs -q python scripts/docs/validate_metadata.py python scripts/docs/validate_sources.py python scripts/docs/validate_links.py @@ -135,4 +230,5 @@ mkdocs build --strict python scripts/docs/validate_search_index.py ``` -`main`에 병합되면 GitHub Actions가 같은 검사를 다시 실행하고 생성된 `site/`을 Pages artifact로 배포합니다. 생성 HTML과 검색 인덱스는 Git에 커밋하지 않습니다. +생성된 `site/`과 검색 인덱스는 커밋하지 않습니다. 실패한 검증을 무시하지 +않습니다. diff --git a/docs/guides/application-development/nodejs-file-io-cpu/index.md b/docs/services/application-development/nodejs-file-io-cpu/index.md similarity index 97% rename from docs/guides/application-development/nodejs-file-io-cpu/index.md rename to docs/services/application-development/nodejs-file-io-cpu/index.md index 60327b2..25c276b 100644 --- a/docs/guides/application-development/nodejs-file-io-cpu/index.md +++ b/docs/services/application-development/nodejs-file-io-cpu/index.md @@ -20,6 +20,8 @@ tags: - performance - diagnostics - development +redirect_from: +- guides/application-development/nodejs-file-io-cpu/index.md --- # Node.js 서버 CPU 오버헤드 분석 및 개선 방안 diff --git a/docs/research/azure-ai-search/a10-vs-t4-embedding-benchmark/index.md b/docs/services/azure-ai-search/custom-vectorization/a10-vs-t4/index.md similarity index 97% rename from docs/research/azure-ai-search/a10-vs-t4-embedding-benchmark/index.md rename to docs/services/azure-ai-search/custom-vectorization/a10-vs-t4/index.md index 320847a..5c0f5cd 100644 --- a/docs/research/azure-ai-search/a10-vs-t4-embedding-benchmark/index.md +++ b/docs/services/azure-ai-search/custom-vectorization/a10-vs-t4/index.md @@ -17,13 +17,16 @@ tags: - benchmarking - performance published_at: 2026-07-23 +topic_order: 5 +redirect_from: +- research/azure-ai-search/a10-vs-t4-embedding-benchmark/index.md --- # A10 vs T4 임베딩 추론 벤치마크: BGE-m3-ko + TEI (Azure Spot VM) **AI Search + Agent 구성(Indexer 미사용, Push API)에서 TEI로 서빙한 dragonkue/BGE-m3-ko의 추론 성능을 A10·T4에서 실측 비교** -> 관련 문서: [GPU vLLM RAG 가이드](../../../guides/azure-ai-search/gpu-vllm-rag/index.md) | [Custom 임베딩 적재 가이드](../../../guides/azure-ai-search/custom-embedding-ingestion/index.md) | [BGE-M3 vs Qwen3 품질 비교](../bge-m3-vs-qwen3-embedding/index.md) +> 관련 문서: [GPU vLLM RAG 가이드](../gpu-vllm/index.md) | [Custom 임베딩 적재 가이드](../index.md) | [BGE-M3 vs Qwen3 품질 비교](../bge-m3-vs-qwen3/index.md) > > 작성일: 2026-07-22 @@ -66,7 +69,7 @@ published_at: 2026-07-23 - NVadsA10v5 시리즈 중 **full-GPU 프로필(NV36ads, A10-24Q 24GB 단독 점유)** 사용. NV6/12/18ads는 GPU fractional 파티션(1/6~1/2) — 본 결과 적용 불가 - T4는 TEI **Turing 전용 이미지(`turing-1.8`) 필수**. Turing은 TEI [experimental 지원](https://github.com/huggingface/text-embeddings-inference#docker-images), Flash Attention 기본 비활성화 - NVIDIA 드라이버는 Azure VM extension `NvidiaGpuDriverLinux`(Microsoft.HpcCompute)로 설치 — GRID/CUDA 자동 판별. A10은 GRID 드라이버 필수 (수동 CUDA 드라이버 설치 시 vGPU 라이선스 실패) -- 실행 스크립트: [bench/](https://github.com/hellices/devguidesample/tree/main/samples/azure-ai-search/source-material/custom_vectorization/bench) +- 실행 스크립트: [bench/](https://github.com/hellices/devguidesample/tree/main/docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench) > ⚠️ **vCPU 격차(4 vs 36)의 결과 왜곡 여부 교차검증 완료.** T4 VM을 NC16as_T4_v3(16 vCPU)로 리사이즈 후 동일 벤치마크 5라운드 재실행: > - 500자 최대 처리량 61.0 texts/s (4 vCPU: 61.3) / 1,000자 22.6 (4 vCPU: 23.4) / 단건 5.86ms (4 vCPU: 5.59ms) — **vCPU 4배 증가에도 측정 오차(±3%) 이내** @@ -244,7 +247,7 @@ Indexer 미사용 구성의 임베딩 호출 경로 2종: ### 쿼리 트래픽 증가 시 증설 기준 — QPS 스윕 실측 -T4 1대(NC4as_T4_v3, TEI turing-1.8, BGE-m3-ko)에 오픈루프 고정 도착률로 단건 쿼리(~40자)를 주입하며 포화 지점을 실측 (30s/단계, 3라운드 스윕 — [bench_qps_sweep.py](https://github.com/hellices/devguidesample/blob/main/samples/azure-ai-search/source-material/custom_vectorization/bench/bench_qps_sweep.py)): +T4 1대(NC4as_T4_v3, TEI turing-1.8, BGE-m3-ko)에 오픈루프 고정 도착률로 단건 쿼리(~40자)를 주입하며 포화 지점을 실측 (30s/단계, 3라운드 스윕 — [bench_qps_sweep.py](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/bench_qps_sweep.py)): | 도착률 (QPS) | p50 | p95 | p99 | GPU util | 판정 | |-------------|-----|-----|-----|----------|------| diff --git a/docs/research/azure-ai-search/bge-m3-vs-qwen3-embedding/index.md b/docs/services/azure-ai-search/custom-vectorization/bge-m3-vs-qwen3/index.md similarity index 99% rename from docs/research/azure-ai-search/bge-m3-vs-qwen3-embedding/index.md rename to docs/services/azure-ai-search/custom-vectorization/bge-m3-vs-qwen3/index.md index bd20a93..7b2e221 100644 --- a/docs/research/azure-ai-search/bge-m3-vs-qwen3-embedding/index.md +++ b/docs/services/azure-ai-search/custom-vectorization/bge-m3-vs-qwen3/index.md @@ -17,6 +17,9 @@ tags: - benchmarking - performance published_at: 2026-05-22 +topic_order: 4 +redirect_from: +- research/azure-ai-search/bge-m3-vs-qwen3-embedding/index.md --- # BGE-M3 vs Qwen3-Embedding-0.6B 벡터 검색 품질 비교 diff --git a/docs/guides/azure-ai-search/custom-web-api-vectorization/images/architecture.png b/docs/services/azure-ai-search/custom-vectorization/custom-web-api/images/architecture.png similarity index 100% rename from docs/guides/azure-ai-search/custom-web-api-vectorization/images/architecture.png rename to docs/services/azure-ai-search/custom-vectorization/custom-web-api/images/architecture.png diff --git a/docs/guides/azure-ai-search/custom-web-api-vectorization/index.md b/docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md similarity index 97% rename from docs/guides/azure-ai-search/custom-web-api-vectorization/index.md rename to docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md index 8a9d486..cec6a93 100644 --- a/docs/guides/azure-ai-search/custom-web-api-vectorization/index.md +++ b/docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md @@ -21,6 +21,9 @@ technologies: tags: - deployment - architecture +topic_order: 2 +redirect_from: +- guides/azure-ai-search/custom-web-api-vectorization/index.md --- # Azure AI Search Custom Web API를 활용한 외부 임베딩 모델 통합 벡터화 가이드 @@ -37,7 +40,7 @@ tags: | 검증 모델 | [BAAI/bge-m3](https://huggingface.co/BAAI/bge-m3), [Qwen/Qwen3-Embedding-0.6B](https://huggingface.co/Qwen/Qwen3-Embedding-0.6B) | | 호스팅 | FastAPI 서버를 Container Apps (TLS 자동) 또는 AKS + cert-manager (Let's Encrypt)로 HTTPS 노출 | | 핵심 검증 | Custom Web API Skill(인덱싱) + Custom Web API Vectorizer(쿼리) 동작 확인 | -| 모델 비교 | [BGE-M3 vs Qwen3-Embedding-0.6B](../../../research/azure-ai-search/bge-m3-vs-qwen3-embedding/index.md) | +| 모델 비교 | [BGE-M3 vs Qwen3-Embedding-0.6B](../bge-m3-vs-qwen3/index.md) | | 인덱서 결과 | 5건 처리 / 0건 실패 | --- @@ -820,7 +823,7 @@ curl -X POST "$SEARCH_URL/indexes/sample-chunk-idx/docs/search?api-version=2024- ### sentence-transformers → TEI 전환 시 차이 -TEI가 sentence-transformers보다 유리한 구조적 이유(dynamic batching, Rust 네이티브 동시성, GPU 최적화)는 [Custom 임베딩 적재 가이드 — 서빙 엔진 비교](../custom-embedding-ingestion/index.md#3-gpu-tei-vs-vllm) 참고. 아래는 이 가이드(CPU 기본 구성) 기준의 실용적 차이: +TEI가 sentence-transformers보다 유리한 구조적 이유(dynamic batching, Rust 네이티브 동시성, GPU 최적화)는 [Custom 임베딩 적재 가이드 — 서빙 엔진 비교](../index.md#3-gpu-tei-vs-vllm) 참고. 아래는 이 가이드(CPU 기본 구성) 기준의 실용적 차이: | | sentence-transformers (현재) | TEI | |---|---|---| @@ -923,10 +926,10 @@ Ingress의 backend 서비스를 `bge-m3-embedding`으로 되돌리면 즉시 sen --- ## 📋 관련 문서 -- [GPU vLLM RAG 가이드](../gpu-vllm-rag/index.md) — T4 GPU에서 vLLM + Push API 대량 적재 + 하이브리드 검색 -- [BGE-M3 vs Qwen3-Embedding-0.6B 비교](../../../research/azure-ai-search/bge-m3-vs-qwen3-embedding/index.md) — 동일 파이프라인에서의 검색 품질 비교 결과 -- [Custom 임베딩 적재 가이드](../custom-embedding-ingestion/index.md) — SKU 선택부터 서빙 엔진까지 의사결정 가이드 -- [청킹 전략 리서치](../../../research/azure-ai-search/rag-chunking-strategies/index.md) — 7가지 최신 RAG 청킹 전략 비교 분석 +- [GPU vLLM RAG 가이드](../gpu-vllm/index.md) — T4 GPU에서 vLLM + Push API 대량 적재 + 하이브리드 검색 +- [BGE-M3 vs Qwen3-Embedding-0.6B 비교](../bge-m3-vs-qwen3/index.md) — 동일 파이프라인에서의 검색 품질 비교 결과 +- [Custom 임베딩 적재 가이드](../index.md) — SKU 선택부터 서빙 엔진까지 의사결정 가이드 +- [청킹 전략 리서치](../rag-chunking/index.md) — 7가지 최신 RAG 청킹 전략 비교 분석 ## 📋 참고 diff --git a/docs/guides/azure-ai-search/gpu-vllm-rag/images/gpu_vllm_rag_architecture.png b/docs/services/azure-ai-search/custom-vectorization/gpu-vllm/images/gpu_vllm_rag_architecture.png similarity index 100% rename from docs/guides/azure-ai-search/gpu-vllm-rag/images/gpu_vllm_rag_architecture.png rename to docs/services/azure-ai-search/custom-vectorization/gpu-vllm/images/gpu_vllm_rag_architecture.png diff --git a/docs/guides/azure-ai-search/gpu-vllm-rag/index.md b/docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md similarity index 96% rename from docs/guides/azure-ai-search/gpu-vllm-rag/index.md rename to docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md index 5333685..2776de2 100644 --- a/docs/guides/azure-ai-search/gpu-vllm-rag/index.md +++ b/docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md @@ -22,13 +22,16 @@ tags: - performance - deployment - architecture +topic_order: 3 +redirect_from: +- guides/azure-ai-search/gpu-vllm-rag/index.md --- # GPU vLLM RAG 가이드: Push API 대량 적재 + 실시간 하이브리드 검색 **T4 GPU에서 vLLM 임베딩 + PIC 청킹을 통합 서빙하고, Push API로 대량 적재 + Custom Vectorizer로 실시간 하이브리드 검색을 수행하는 구성 가이드** -> 관련 문서: [기본 가이드 (CPU, Indexer)](../custom-web-api-vectorization/index.md) | [Custom 임베딩 적재 가이드](../custom-embedding-ingestion/index.md) | [청킹 전략 리서치](../../../research/azure-ai-search/rag-chunking-strategies/index.md) +> 관련 문서: [기본 가이드 (CPU, Indexer)](../custom-web-api/index.md) | [Custom 임베딩 적재 가이드](../index.md) | [청킹 전략 리서치](../rag-chunking/index.md) > > 작성일: 2026-05-22 @@ -151,7 +154,7 @@ curl -X PUT "$SEARCH_URL/indexes/prod-chunk-idx?api-version=2024-07-01" \ ## 2. Push API 파이프라인 -인덱서 대신 Push API로 적재한다. 인덱서의 구조적 한계(deg≤10, 순차 배치, 공용 2h/전용 24h 제한)를 우회하여 GPU 활용률을 극대화한다. ([상세 비교](../custom-embedding-ingestion/index.md)) +인덱서 대신 Push API로 적재한다. 인덱서의 구조적 한계(deg≤10, 순차 배치, 공용 2h/전용 24h 제한)를 우회하여 GPU 활용률을 극대화한다. ([상세 비교](../index.md)) 파이프라인 흐름: **데이터 소스 → PIC 청킹(vLLM-chunk) → 임베딩(vLLM-embed) → Push API(AI Search)** @@ -160,7 +163,7 @@ curl -X PUT "$SEARCH_URL/indexes/prod-chunk-idx?api-version=2024-07-01" \ - 적재: Push API 배치 제한 (요청당 최대 1000건 또는 16MB) - Fallback: PIC 실패 시 원본 텍스트를 단일 청크로 적재 -> Indexer vs Push API의 개념적 비교와 하이브리드 운영 패턴은 [Custom 임베딩 적재 가이드 — 적재 방식](../custom-embedding-ingestion/index.md#2-indexer-vs-push-api) 참고. 실측치(T4 Spot 3대, vLLM 3 replica): 500 docs 적재 147.9s (embed 37.71 c/s). 상세 결과는 [Appendix B](#appendix-b) 참고. +> Indexer vs Push API의 개념적 비교와 하이브리드 운영 패턴은 [Custom 임베딩 적재 가이드 — 적재 방식](../index.md#2-indexer-vs-push-api) 참고. 실측치(T4 Spot 3대, vLLM 3 replica): 500 docs 적재 147.9s (embed 37.71 c/s). 상세 결과는 [Appendix B](#appendix-b) 참고. --- @@ -586,9 +589,9 @@ curl localhost:8081/metrics | grep -E "vllm:(num_requests|gpu_cache)" - [vLLM Embedding Models](https://docs.vllm.ai/en/latest/models/pooling_models.html) - [Qwen3-Embedding 모델 라인업](https://huggingface.co/collections/Qwen/qwen3-embedding-67e5253e22bb421b4fa0ed10) - [PIC — Pseudo-Instruction Chunking](https://aclanthology.org/2025.findings-acl.422/) (ACL-Findings 2025) -- [기본 가이드 (CPU, Indexer)](../custom-web-api-vectorization/index.md) -- [Custom 임베딩 적재 가이드](../custom-embedding-ingestion/index.md) -- [청킹 전략 리서치](../../../research/azure-ai-search/rag-chunking-strategies/index.md) +- [기본 가이드 (CPU, Indexer)](../custom-web-api/index.md) +- [Custom 임베딩 적재 가이드](../index.md) +- [청킹 전략 리서치](../rag-chunking/index.md) --- @@ -602,13 +605,13 @@ curl localhost:8081/metrics | grep -E "vllm:(num_requests|gpu_cache)" | vLLM + BGE-M3 | 0.57B 모델에서 PyTorch overhead가 크다 (23.85 c/s) | | TEI + BGE-M3 | **51.11 c/s로 가장 빠르지만**, MTEB 성능이 Qwen3-4B 대비 낮아 품질·속도 트레이드오프 | | vLLM + Qwen3-Embedding-8B | 8B(16GB)이면 T4 VRAM 전체를 소비, 청킹 LLM 공간 없음 | -| sentence-transformers | 서버 레벨 dynamic batching 미지원, Python 단일 프로세스 동시성 제한 ([상세](../custom-embedding-ingestion/index.md)) | +| sentence-transformers | 서버 레벨 dynamic batching 미지원, Python 단일 프로세스 동시성 제한 ([상세](../index.md)) | vLLM은 PyTorch 기반이라 **Qwen3 아키텍처를 HuggingFace 가중치에서 직접 로딩** 가능하다. TEI(Rust/candle)는 모델별 커널을 별도 구현해야 하므로 신규 아키텍처 지원이 늦다. 단, BERT 계열(BGE-M3)에서는 TEI가 2.1배 빠르다 ([Appendix B](#appendix-b) 참고). ### 청킹: PIC (Pseudo-Instruction Chunking) -[청킹 전략 리서치](../../../research/azure-ai-search/rag-chunking-strategies/index.md)에서 분석한 7가지 전략 중 PIC를 선택한 이유: +[청킹 전략 리서치](../rag-chunking/index.md)에서 분석한 7가지 전략 중 PIC를 선택한 이유: | 전략 | LLM 호출 | GPU 적합도 | 검색 품질 | 인덱싱 속도 | 선택 | |------|---------|-----------|----------|-----------|------| diff --git a/docs/guides/azure-ai-search/custom-embedding-ingestion/images/indexer_vs_pushapi.png b/docs/services/azure-ai-search/custom-vectorization/images/indexer_vs_pushapi.png similarity index 100% rename from docs/guides/azure-ai-search/custom-embedding-ingestion/images/indexer_vs_pushapi.png rename to docs/services/azure-ai-search/custom-vectorization/images/indexer_vs_pushapi.png diff --git a/docs/guides/azure-ai-search/custom-embedding-ingestion/index.md b/docs/services/azure-ai-search/custom-vectorization/index.md similarity index 98% rename from docs/guides/azure-ai-search/custom-embedding-ingestion/index.md rename to docs/services/azure-ai-search/custom-vectorization/index.md index a596958..eb8354e 100644 --- a/docs/guides/azure-ai-search/custom-embedding-ingestion/index.md +++ b/docs/services/azure-ai-search/custom-vectorization/index.md @@ -21,13 +21,15 @@ tags: - architecture - performance - deployment +redirect_from: +- guides/azure-ai-search/custom-embedding-ingestion/index.md --- # Custom 임베딩 적재 가이드: SKU 선택부터 서빙 엔진까지 **Azure AI Search에 벡터를 적재하는 두 가지 경로(Indexer Pull vs Push API)와, GPU 임베딩 서빙 엔진(TEI vs vLLM) 선택 기준** -> 관련 문서: [기본 가이드 (CPU, Indexer)](../custom-web-api-vectorization/index.md) | [GPU vLLM RAG 가이드](../gpu-vllm-rag/index.md) | [청킹 전략 리서치](../../../research/azure-ai-search/rag-chunking-strategies/index.md) +> 관련 문서: [기본 가이드 (CPU, Indexer)](custom-web-api/index.md) | [GPU vLLM RAG 가이드](gpu-vllm/index.md) | [청킹 전략 리서치](rag-chunking/index.md) > > 작성일: 2026-05-22 @@ -293,7 +295,7 @@ TEI / vLLM (추론 서버) (어댑터 필요: 쿼리 시점) ``` -> 어댑터 구현 예시: [tei-adapter](https://github.com/hellices/devguidesample/tree/main/samples/azure-ai-search/source-material/custom_vectorization/tei-adapter) 디렉토리 참고 +> 어댑터 구현 예시: [tei-adapter](https://github.com/hellices/devguidesample/tree/main/docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter) 디렉토리 참고 --- diff --git a/docs/research/azure-ai-search/rag-chunking-strategies/index.md b/docs/services/azure-ai-search/custom-vectorization/rag-chunking/index.md similarity index 99% rename from docs/research/azure-ai-search/rag-chunking-strategies/index.md rename to docs/services/azure-ai-search/custom-vectorization/rag-chunking/index.md index 84ad2b9..34618ca 100644 --- a/docs/research/azure-ai-search/rag-chunking-strategies/index.md +++ b/docs/services/azure-ai-search/custom-vectorization/rag-chunking/index.md @@ -16,13 +16,16 @@ tags: - architecture - performance published_at: 2026-05-22 +topic_order: 1 +redirect_from: +- research/azure-ai-search/rag-chunking-strategies/index.md --- # RAG Chunking 전략 리서치 보고서 **Azure AI Search Custom Web API 파이프라인에서 활용 가능한 최신 Chunking 전략, 프레임워크, 논문 정리** -> 작성일: 2026-05-22 | 관련 문서: [Custom Web API 벡터화 가이드](../../../guides/azure-ai-search/custom-web-api-vectorization/index.md) +> 작성일: 2026-05-22 | 관련 문서: [Custom Web API 벡터화 가이드](../custom-web-api/index.md) --- diff --git a/docs/services/azure-ai-search/custom-vectorization/samples/implementation/README.md b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/README.md new file mode 100644 index 0000000..fab11c4 --- /dev/null +++ b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/README.md @@ -0,0 +1,5 @@ +# Custom vectorization implementation + +This runnable sample contains the embedding and reranker adapters, benchmark +scripts, Kubernetes manifests, and architecture source files used by the custom +vectorization topic. diff --git a/samples/azure-ai-search/source-material/custom_vectorization/bench/Dockerfile b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/Dockerfile similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/bench/Dockerfile rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/Dockerfile diff --git a/samples/azure-ai-search/source-material/custom_vectorization/bench/bench_embed_gpu.py b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/bench_embed_gpu.py similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/bench/bench_embed_gpu.py rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/bench_embed_gpu.py diff --git a/samples/azure-ai-search/source-material/custom_vectorization/bench/bench_qps_sweep.py b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/bench_qps_sweep.py similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/bench/bench_qps_sweep.py rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/bench_qps_sweep.py diff --git a/samples/azure-ai-search/source-material/custom_vectorization/bench/bench_runner.py b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/bench_runner.py similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/bench/bench_runner.py rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/bench_runner.py diff --git a/samples/azure-ai-search/source-material/custom_vectorization/bench/k8s/configmap.yaml b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/k8s/configmap.yaml similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/bench/k8s/configmap.yaml rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/k8s/configmap.yaml diff --git a/samples/azure-ai-search/source-material/custom_vectorization/bench/k8s/job-bench-concurrency.yaml b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/k8s/job-bench-concurrency.yaml similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/bench/k8s/job-bench-concurrency.yaml rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/k8s/job-bench-concurrency.yaml diff --git a/samples/azure-ai-search/source-material/custom_vectorization/bench/k8s/job-bench-indexer.yaml b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/k8s/job-bench-indexer.yaml similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/bench/k8s/job-bench-indexer.yaml rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/k8s/job-bench-indexer.yaml diff --git a/samples/azure-ai-search/source-material/custom_vectorization/bench/k8s/job-bench-push.yaml b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/k8s/job-bench-push.yaml similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/bench/k8s/job-bench-push.yaml rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/k8s/job-bench-push.yaml diff --git a/samples/azure-ai-search/source-material/custom_vectorization/bench/requirements.txt b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/requirements.txt similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/bench/requirements.txt rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/requirements.txt diff --git a/samples/azure-ai-search/source-material/custom_vectorization/embedding-api/Dockerfile b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/embedding-api/Dockerfile similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/embedding-api/Dockerfile rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/embedding-api/Dockerfile diff --git a/samples/azure-ai-search/source-material/custom_vectorization/embedding-api/app.py b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/embedding-api/app.py similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/embedding-api/app.py rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/embedding-api/app.py diff --git a/samples/azure-ai-search/source-material/custom_vectorization/embedding-api/requirements.txt b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/embedding-api/requirements.txt similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/embedding-api/requirements.txt rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/embedding-api/requirements.txt diff --git a/samples/azure-ai-search/source-material/custom_vectorization/images/architecture.excalidraw b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/images/architecture.excalidraw similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/images/architecture.excalidraw rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/images/architecture.excalidraw diff --git a/samples/azure-ai-search/source-material/custom_vectorization/images/architecture.png b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/images/architecture.png similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/images/architecture.png rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/images/architecture.png diff --git a/samples/azure-ai-search/source-material/custom_vectorization/images/architecture_lite.png b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/images/architecture_lite.png similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/images/architecture_lite.png rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/images/architecture_lite.png diff --git a/samples/azure-ai-search/source-material/custom_vectorization/images/gpu_vllm_rag_architecture.excalidraw b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/images/gpu_vllm_rag_architecture.excalidraw similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/images/gpu_vllm_rag_architecture.excalidraw rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/images/gpu_vllm_rag_architecture.excalidraw diff --git a/samples/azure-ai-search/source-material/custom_vectorization/images/gpu_vllm_rag_architecture.png b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/images/gpu_vllm_rag_architecture.png similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/images/gpu_vllm_rag_architecture.png rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/images/gpu_vllm_rag_architecture.png diff --git a/samples/azure-ai-search/source-material/custom_vectorization/images/indexer_vs_pushapi.png b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/images/indexer_vs_pushapi.png similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/images/indexer_vs_pushapi.png rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/images/indexer_vs_pushapi.png diff --git a/samples/azure-ai-search/source-material/custom_vectorization/indexer_vs_pushapi.excalidraw b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/indexer_vs_pushapi.excalidraw similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/indexer_vs_pushapi.excalidraw rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/indexer_vs_pushapi.excalidraw diff --git a/samples/azure-ai-search/source-material/custom_vectorization/k8s/cluster-issuer.yaml b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/k8s/cluster-issuer.yaml similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/k8s/cluster-issuer.yaml rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/k8s/cluster-issuer.yaml diff --git a/samples/azure-ai-search/source-material/custom_vectorization/k8s/deployment.yaml b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/k8s/deployment.yaml similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/k8s/deployment.yaml rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/k8s/deployment.yaml diff --git a/samples/azure-ai-search/source-material/custom_vectorization/k8s/ingress.yaml b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/k8s/ingress.yaml similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/k8s/ingress.yaml rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/k8s/ingress.yaml diff --git a/samples/azure-ai-search/source-material/custom_vectorization/reranker-adapter/Dockerfile b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/reranker-adapter/Dockerfile similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/reranker-adapter/Dockerfile rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/reranker-adapter/Dockerfile diff --git a/samples/azure-ai-search/source-material/custom_vectorization/reranker-adapter/app.py b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/reranker-adapter/app.py similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/reranker-adapter/app.py rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/reranker-adapter/app.py diff --git a/samples/azure-ai-search/source-material/custom_vectorization/reranker-adapter/requirements.txt b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/reranker-adapter/requirements.txt similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/reranker-adapter/requirements.txt rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/reranker-adapter/requirements.txt diff --git a/docs/services/azure-ai-search/custom-vectorization/samples/implementation/sample.yml b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/sample.yml new file mode 100644 index 0000000..77a6742 --- /dev/null +++ b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/sample.yml @@ -0,0 +1,10 @@ +title: Custom vectorization implementation +description: Runnable embedding adapters, benchmarks, and Kubernetes manifests. +kind: runnable +used_by: +- index +- rag-chunking +- custom-web-api +- gpu-vllm +- bge-m3-vs-qwen3 +- a10-vs-t4 diff --git a/samples/azure-ai-search/source-material/custom_vectorization/tei-adapter/Dockerfile b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter/Dockerfile similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/tei-adapter/Dockerfile rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter/Dockerfile diff --git a/samples/azure-ai-search/source-material/custom_vectorization/tei-adapter/app.py b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter/app.py similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/tei-adapter/app.py rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter/app.py diff --git a/samples/azure-ai-search/source-material/custom_vectorization/tei-adapter/k8s/deployment.yaml b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter/k8s/deployment.yaml similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/tei-adapter/k8s/deployment.yaml rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter/k8s/deployment.yaml diff --git a/samples/azure-ai-search/source-material/custom_vectorization/tei-adapter/requirements.txt b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter/requirements.txt similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/tei-adapter/requirements.txt rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter/requirements.txt diff --git a/samples/azure-ai-search/source-material/custom_vectorization/vllm-adapter/Dockerfile b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/vllm-adapter/Dockerfile similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/vllm-adapter/Dockerfile rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/vllm-adapter/Dockerfile diff --git a/samples/azure-ai-search/source-material/custom_vectorization/vllm-adapter/app.py b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/vllm-adapter/app.py similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/vllm-adapter/app.py rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/vllm-adapter/app.py diff --git a/samples/azure-ai-search/source-material/custom_vectorization/vllm-adapter/requirements.txt b/docs/services/azure-ai-search/custom-vectorization/samples/implementation/vllm-adapter/requirements.txt similarity index 100% rename from samples/azure-ai-search/source-material/custom_vectorization/vllm-adapter/requirements.txt rename to docs/services/azure-ai-search/custom-vectorization/samples/implementation/vllm-adapter/requirements.txt diff --git a/docs/cases/azure-ai-search/eventual-consistency-reindex/index.md b/docs/services/azure-ai-search/eventual-consistency-reindex/index.md similarity index 97% rename from docs/cases/azure-ai-search/eventual-consistency-reindex/index.md rename to docs/services/azure-ai-search/eventual-consistency-reindex/index.md index ab12d5e..0f088ea 100644 --- a/docs/cases/azure-ai-search/eventual-consistency-reindex/index.md +++ b/docs/services/azure-ai-search/eventual-consistency-reindex/index.md @@ -16,6 +16,8 @@ tags: - reliability - troubleshooting occurred_at: 2026-04-17 +redirect_from: +- cases/azure-ai-search/eventual-consistency-reindex/index.md --- # Azure AI Search 버전 기반 재색인의 Eventual Consistency 이슈 @@ -171,7 +173,7 @@ Step 3가 완전히 결정적이 되고 replica lag의 영향을 받지 않습 ## 직접 재현해 보기 -자급식 재현 스크립트: [repro.py](https://github.com/hellices/devguidesample/blob/main/samples/azure-ai-search/source-material/repro.py). Azure AI Search 서비스가 미리 준비되어 있어야 합니다. +자급식 재현 스크립트: [repro.py](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-ai-search/eventual-consistency-reindex/samples/reproduction/repro.py). Azure AI Search 서비스가 미리 준비되어 있어야 합니다. ```bash # 1. Azure AI Search 서비스 생성 (35k 문서면 Basic 도 충분. diff --git a/docs/services/azure-ai-search/eventual-consistency-reindex/samples/reproduction/README.md b/docs/services/azure-ai-search/eventual-consistency-reindex/samples/reproduction/README.md new file mode 100644 index 0000000..2078106 --- /dev/null +++ b/docs/services/azure-ai-search/eventual-consistency-reindex/samples/reproduction/README.md @@ -0,0 +1,4 @@ +# Eventual consistency reproduction + +Run `repro.py` against an Azure AI Search service to reproduce the indexing +and reindexing behavior described by the case. diff --git a/samples/azure-ai-search/source-material/repro.py b/docs/services/azure-ai-search/eventual-consistency-reindex/samples/reproduction/repro.py similarity index 100% rename from samples/azure-ai-search/source-material/repro.py rename to docs/services/azure-ai-search/eventual-consistency-reindex/samples/reproduction/repro.py diff --git a/docs/services/azure-ai-search/eventual-consistency-reindex/samples/reproduction/sample.yml b/docs/services/azure-ai-search/eventual-consistency-reindex/samples/reproduction/sample.yml new file mode 100644 index 0000000..3aa19f5 --- /dev/null +++ b/docs/services/azure-ai-search/eventual-consistency-reindex/samples/reproduction/sample.yml @@ -0,0 +1,5 @@ +title: Eventual consistency reproduction +description: Runnable script that reproduces the indexing consistency scenario. +kind: runnable +used_by: +- index diff --git a/docs/guides/azure-ai-search/korean-analyzer-comparison/images/portal_ko_microsoft.png b/docs/services/azure-ai-search/korean-analyzer-comparison/images/portal_ko_microsoft.png similarity index 100% rename from docs/guides/azure-ai-search/korean-analyzer-comparison/images/portal_ko_microsoft.png rename to docs/services/azure-ai-search/korean-analyzer-comparison/images/portal_ko_microsoft.png diff --git a/docs/guides/azure-ai-search/korean-analyzer-comparison/images/portal_standard_lucene.png b/docs/services/azure-ai-search/korean-analyzer-comparison/images/portal_standard_lucene.png similarity index 100% rename from docs/guides/azure-ai-search/korean-analyzer-comparison/images/portal_standard_lucene.png rename to docs/services/azure-ai-search/korean-analyzer-comparison/images/portal_standard_lucene.png diff --git a/docs/guides/azure-ai-search/korean-analyzer-comparison/index.md b/docs/services/azure-ai-search/korean-analyzer-comparison/index.md similarity index 99% rename from docs/guides/azure-ai-search/korean-analyzer-comparison/index.md rename to docs/services/azure-ai-search/korean-analyzer-comparison/index.md index 3178adb..11b6365 100644 --- a/docs/guides/azure-ai-search/korean-analyzer-comparison/index.md +++ b/docs/services/azure-ai-search/korean-analyzer-comparison/index.md @@ -19,6 +19,8 @@ technologies: tags: - diagnostics - performance +redirect_from: +- guides/azure-ai-search/korean-analyzer-comparison/index.md --- # Azure AI Search 한국어 분석기(Analyzer) 비교 가이드 diff --git a/samples/azure-ai-search/analyzer-comparison/.env.example b/docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/.env.example similarity index 100% rename from samples/azure-ai-search/analyzer-comparison/.env.example rename to docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/.env.example diff --git a/samples/azure-ai-search/analyzer-comparison/.gitignore b/docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/.gitignore similarity index 100% rename from samples/azure-ai-search/analyzer-comparison/.gitignore rename to docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/.gitignore diff --git a/docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/README.md b/docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/README.md new file mode 100644 index 0000000..8256dfe --- /dev/null +++ b/docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/README.md @@ -0,0 +1,4 @@ +# Korean analyzer comparison + +Runnable Python checks, sample data, and captured portal images used to compare +Korean analyzers and synonym behavior. diff --git a/samples/azure-ai-search/analyzer-comparison/analyzer_test.py b/docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/analyzer_test.py similarity index 100% rename from samples/azure-ai-search/analyzer-comparison/analyzer_test.py rename to docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/analyzer_test.py diff --git a/samples/azure-ai-search/analyzer-comparison/images/.gitkeep b/docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/images/.gitkeep similarity index 100% rename from samples/azure-ai-search/analyzer-comparison/images/.gitkeep rename to docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/images/.gitkeep diff --git a/samples/azure-ai-search/analyzer-comparison/images/index_analyzer_type.png b/docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/images/index_analyzer_type.png similarity index 100% rename from samples/azure-ai-search/analyzer-comparison/images/index_analyzer_type.png rename to docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/images/index_analyzer_type.png diff --git a/samples/azure-ai-search/analyzer-comparison/images/portal_ko_microsoft.png b/docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/images/portal_ko_microsoft.png similarity index 100% rename from samples/azure-ai-search/analyzer-comparison/images/portal_ko_microsoft.png rename to docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/images/portal_ko_microsoft.png diff --git a/samples/azure-ai-search/analyzer-comparison/images/portal_standard_lucene.png b/docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/images/portal_standard_lucene.png similarity index 100% rename from samples/azure-ai-search/analyzer-comparison/images/portal_standard_lucene.png rename to docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/images/portal_standard_lucene.png diff --git a/samples/azure-ai-search/analyzer-comparison/requirements.txt b/docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/requirements.txt similarity index 100% rename from samples/azure-ai-search/analyzer-comparison/requirements.txt rename to docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/requirements.txt diff --git a/docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/sample.yml b/docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/sample.yml new file mode 100644 index 0000000..0bf62fa --- /dev/null +++ b/docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/sample.yml @@ -0,0 +1,5 @@ +title: Korean analyzer comparison +description: Runnable analyzer and synonym comparison scripts with sample data. +kind: runnable +used_by: +- index diff --git a/samples/azure-ai-search/analyzer-comparison/sample_data.json b/docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/sample_data.json similarity index 100% rename from samples/azure-ai-search/analyzer-comparison/sample_data.json rename to docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/sample_data.json diff --git a/samples/azure-ai-search/analyzer-comparison/synonym_test.py b/docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/synonym_test.py similarity index 100% rename from samples/azure-ai-search/analyzer-comparison/synonym_test.py rename to docs/services/azure-ai-search/korean-analyzer-comparison/samples/analyzer-comparison/synonym_test.py diff --git a/docs/guides/azure-application-gateway/sse-response-buffering/index.md b/docs/services/azure-application-gateway/sse-response-buffering/index.md similarity index 98% rename from docs/guides/azure-application-gateway/sse-response-buffering/index.md rename to docs/services/azure-application-gateway/sse-response-buffering/index.md index 70d8ffb..e83b8c2 100644 --- a/docs/guides/azure-application-gateway/sse-response-buffering/index.md +++ b/docs/services/azure-application-gateway/sse-response-buffering/index.md @@ -19,6 +19,8 @@ technologies: tags: - networking - performance +redirect_from: +- guides/azure-application-gateway/sse-response-buffering/index.md --- # Application Gateway SSE 통신 시 Response Buffer 비활성화 가이드 diff --git a/docs/guides/azure-application-gateway/waf-path-ip-allowlist/index.md b/docs/services/azure-application-gateway/waf-path-ip-allowlist/index.md similarity index 99% rename from docs/guides/azure-application-gateway/waf-path-ip-allowlist/index.md rename to docs/services/azure-application-gateway/waf-path-ip-allowlist/index.md index 63d6d14..cb54a78 100644 --- a/docs/guides/azure-application-gateway/waf-path-ip-allowlist/index.md +++ b/docs/services/azure-application-gateway/waf-path-ip-allowlist/index.md @@ -19,6 +19,8 @@ technologies: tags: - security - networking +redirect_from: +- guides/azure-application-gateway/waf-path-ip-allowlist/index.md --- # Application Gateway WAF: 경로 기반 IP 화이트리스트 설정 가이드 diff --git a/docs/guides/azure-architecture/response-time-optimization/index.md b/docs/services/azure-architecture/response-time-optimization/index.md similarity index 96% rename from docs/guides/azure-architecture/response-time-optimization/index.md rename to docs/services/azure-architecture/response-time-optimization/index.md index 1221d37..f4c0296 100644 --- a/docs/guides/azure-architecture/response-time-optimization/index.md +++ b/docs/services/azure-architecture/response-time-optimization/index.md @@ -20,6 +20,8 @@ tags: - architecture - latency - performance +redirect_from: +- guides/azure-architecture/response-time-optimization/index.md --- # 응답속도 최적화를 위한 azure architect 고려사항 diff --git a/docs/guides/azure-automation/portal-cli-limitations/index.md b/docs/services/azure-automation/portal-cli-limitations/index.md similarity index 98% rename from docs/guides/azure-automation/portal-cli-limitations/index.md rename to docs/services/azure-automation/portal-cli-limitations/index.md index 07cc0db..471e7cc 100644 --- a/docs/guides/azure-automation/portal-cli-limitations/index.md +++ b/docs/services/azure-automation/portal-cli-limitations/index.md @@ -20,6 +20,8 @@ technologies: tags: - troubleshooting - development +redirect_from: +- guides/azure-automation/portal-cli-limitations/index.md --- # Azure Automation - 포탈/CLI 제한사항 및 우회 방법 diff --git a/docs/guides/azure-cosmos-db/nodejs-client-optimization/index.md b/docs/services/azure-cosmos-db/nodejs-client-optimization/index.md similarity index 99% rename from docs/guides/azure-cosmos-db/nodejs-client-optimization/index.md rename to docs/services/azure-cosmos-db/nodejs-client-optimization/index.md index f7aa0e1..376c3ba 100644 --- a/docs/guides/azure-cosmos-db/nodejs-client-optimization/index.md +++ b/docs/services/azure-cosmos-db/nodejs-client-optimization/index.md @@ -20,6 +20,8 @@ technologies: tags: - performance - development +redirect_from: +- guides/azure-cosmos-db/nodejs-client-optimization/index.md --- # CosmosDB Client 최적화 방안 @@ -243,4 +245,3 @@ app.post('/users', async (req, res) => { ## 참고 자료 - [Cosmos DB Connection Policy](https://learn.microsoft.com/javascript/api/@azure/cosmos/connectionpolicy) - diff --git a/docs/guides/azure-cosmos-db/nodejs-dns-lookup-bottleneck/index.md b/docs/services/azure-cosmos-db/nodejs-dns-lookup-bottleneck/index.md similarity index 97% rename from docs/guides/azure-cosmos-db/nodejs-dns-lookup-bottleneck/index.md rename to docs/services/azure-cosmos-db/nodejs-dns-lookup-bottleneck/index.md index f864ad5..940186e 100644 --- a/docs/guides/azure-cosmos-db/nodejs-dns-lookup-bottleneck/index.md +++ b/docs/services/azure-cosmos-db/nodejs-dns-lookup-bottleneck/index.md @@ -21,6 +21,8 @@ tags: - networking - performance - troubleshooting +redirect_from: +- guides/azure-cosmos-db/nodejs-dns-lookup-bottleneck/index.md --- # **Cosmos DB 연속 쿼리 시 DNS Lookup 병목 최적화 가이드** diff --git a/docs/guides/azure-cosmos-db/point-read-optimization/index.md b/docs/services/azure-cosmos-db/point-read-optimization/index.md similarity index 99% rename from docs/guides/azure-cosmos-db/point-read-optimization/index.md rename to docs/services/azure-cosmos-db/point-read-optimization/index.md index bec7471..dd3af02 100644 --- a/docs/guides/azure-cosmos-db/point-read-optimization/index.md +++ b/docs/services/azure-cosmos-db/point-read-optimization/index.md @@ -20,6 +20,8 @@ technologies: tags: - performance - latency +redirect_from: +- guides/azure-cosmos-db/point-read-optimization/index.md --- # CosmosDB Point Read를 활용한 트래픽 및 Latency 최적화 전략 diff --git a/docs/guides/azure-database-for-mysql/blue-green-upgrade/index.md b/docs/services/azure-database-for-mysql/blue-green-upgrade/index.md similarity index 99% rename from docs/guides/azure-database-for-mysql/blue-green-upgrade/index.md rename to docs/services/azure-database-for-mysql/blue-green-upgrade/index.md index 94b0420..77d978a 100644 --- a/docs/guides/azure-database-for-mysql/blue-green-upgrade/index.md +++ b/docs/services/azure-database-for-mysql/blue-green-upgrade/index.md @@ -22,6 +22,8 @@ tags: - migration - high-availability - reliability +redirect_from: +- guides/azure-database-for-mysql/blue-green-upgrade/index.md --- # Azure MySQL 무중단 업그레이드: Blue/Green 전략 구현 (AKS + Node.js mysql2 PoolCluster) diff --git a/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/README.md b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/README.md new file mode 100644 index 0000000..2f675d3 --- /dev/null +++ b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/README.md @@ -0,0 +1,4 @@ +# Node.js blue-green upgrade simulation + +This runnable sample contains the application, infrastructure, Kubernetes +manifest, and operational scripts for the blue-green upgrade simulation. diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/app/Dockerfile b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/app/Dockerfile similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/app/Dockerfile rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/app/Dockerfile diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/app/dbHandler.js b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/app/dbHandler.js similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/app/dbHandler.js rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/app/dbHandler.js diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/app/package.json b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/app/package.json similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/app/package.json rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/app/package.json diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/app/server.js b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/app/server.js similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/app/server.js rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/app/server.js diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/infra/main.bicep b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/infra/main.bicep similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/infra/main.bicep rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/infra/main.bicep diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/infra/main.bicepparam b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/infra/main.bicepparam similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/infra/main.bicepparam rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/infra/main.bicepparam diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/k8s/deployment.yaml b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/k8s/deployment.yaml similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/k8s/deployment.yaml rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/k8s/deployment.yaml diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/mysql_change.png b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/mysql_change.png similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/mysql_change.png rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/mysql_change.png diff --git a/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/sample.yml b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/sample.yml new file mode 100644 index 0000000..9c65890 --- /dev/null +++ b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/sample.yml @@ -0,0 +1,5 @@ +title: Node.js blue-green upgrade simulation +description: Runnable application and deployment assets for upgrade rehearsals. +kind: runnable +used_by: +- index diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/scripts/01-deploy-infra.ps1 b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/scripts/01-deploy-infra.ps1 similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/scripts/01-deploy-infra.ps1 rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/scripts/01-deploy-infra.ps1 diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/scripts/02-setup-db.ps1 b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/scripts/02-setup-db.ps1 similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/scripts/02-setup-db.ps1 rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/scripts/02-setup-db.ps1 diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/scripts/02b-setup-replication.ps1 b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/scripts/02b-setup-replication.ps1 similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/scripts/02b-setup-replication.ps1 rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/scripts/02b-setup-replication.ps1 diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/scripts/03-build-push.ps1 b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/scripts/03-build-push.ps1 similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/scripts/03-build-push.ps1 rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/scripts/03-build-push.ps1 diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/scripts/04-watch-logs.ps1 b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/scripts/04-watch-logs.ps1 similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/scripts/04-watch-logs.ps1 rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/scripts/04-watch-logs.ps1 diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/scripts/05-cutover.ps1 b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/scripts/05-cutover.ps1 similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/scripts/05-cutover.ps1 rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/scripts/05-cutover.ps1 diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/scripts/06-rollback-or-cleanup.ps1 b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/scripts/06-rollback-or-cleanup.ps1 similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/scripts/06-rollback-or-cleanup.ps1 rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/scripts/06-rollback-or-cleanup.ps1 diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/scripts/run-3-cutovers.ps1 b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/scripts/run-3-cutovers.ps1 similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/scripts/run-3-cutovers.ps1 rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/scripts/run-3-cutovers.ps1 diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/simulation-architecture.excalidraw b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/simulation-architecture.excalidraw similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/simulation-architecture.excalidraw rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/simulation-architecture.excalidraw diff --git a/samples/azure-database-for-mysql/source-material/nodejs/simulation/simulation-cutover.excalidraw b/docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/simulation-cutover.excalidraw similarity index 100% rename from samples/azure-database-for-mysql/source-material/nodejs/simulation/simulation-cutover.excalidraw rename to docs/services/azure-database-for-mysql/blue-green-upgrade/samples/nodejs-simulation/simulation-cutover.excalidraw diff --git a/docs/guides/azure-database-for-mysql/nodejs-read-write-routing/index.md b/docs/services/azure-database-for-mysql/nodejs-read-write-routing/index.md similarity index 98% rename from docs/guides/azure-database-for-mysql/nodejs-read-write-routing/index.md rename to docs/services/azure-database-for-mysql/nodejs-read-write-routing/index.md index 217cde1..b1d9ed9 100644 --- a/docs/guides/azure-database-for-mysql/nodejs-read-write-routing/index.md +++ b/docs/services/azure-database-for-mysql/nodejs-read-write-routing/index.md @@ -21,6 +21,8 @@ tags: - high-availability - performance - development +redirect_from: +- guides/azure-database-for-mysql/nodejs-read-write-routing/index.md --- 아래는 **Node.js에서 mysql2를 사용해 Azure Database for MySQL Flexible Server를 Primary(쓰기) + Replica(읽기) 구성 시 적용할 수 있는 한글 가이드**입니다. Microsoft Learn 공식 문서 링크도 포함했습니다. diff --git a/docs/guides/azure-kubernetes-service/argocd-image-updater-acr/index.md b/docs/services/azure-kubernetes-service/argocd-image-updater-acr/index.md similarity index 99% rename from docs/guides/azure-kubernetes-service/argocd-image-updater-acr/index.md rename to docs/services/azure-kubernetes-service/argocd-image-updater-acr/index.md index 4560d14..3da21e6 100644 --- a/docs/guides/azure-kubernetes-service/argocd-image-updater-acr/index.md +++ b/docs/services/azure-kubernetes-service/argocd-image-updater-acr/index.md @@ -20,6 +20,8 @@ technologies: tags: - deployment - authentication +redirect_from: +- guides/azure-kubernetes-service/argocd-image-updater-acr/index.md --- # ✅ ArgoCD Image Updater + ACR on AKS 설정 가이드 diff --git a/docs/guides/azure-kubernetes-service/authorization-troubleshooting/index.md b/docs/services/azure-kubernetes-service/authorization-troubleshooting/index.md similarity index 99% rename from docs/guides/azure-kubernetes-service/authorization-troubleshooting/index.md rename to docs/services/azure-kubernetes-service/authorization-troubleshooting/index.md index 5a7f340..3af9222 100644 --- a/docs/guides/azure-kubernetes-service/authorization-troubleshooting/index.md +++ b/docs/services/azure-kubernetes-service/authorization-troubleshooting/index.md @@ -20,6 +20,8 @@ technologies: tags: - authorization - troubleshooting +redirect_from: +- guides/azure-kubernetes-service/authorization-troubleshooting/index.md --- # ✅ AKS 배포 시 Authorization 오류 트러블슈팅 가이드 diff --git a/docs/guides/azure-kubernetes-service/cni-overlay-nsg/index.md b/docs/services/azure-kubernetes-service/cni-overlay-nsg/index.md similarity index 99% rename from docs/guides/azure-kubernetes-service/cni-overlay-nsg/index.md rename to docs/services/azure-kubernetes-service/cni-overlay-nsg/index.md index 50f75c2..a0f0bda 100644 --- a/docs/guides/azure-kubernetes-service/cni-overlay-nsg/index.md +++ b/docs/services/azure-kubernetes-service/cni-overlay-nsg/index.md @@ -19,6 +19,8 @@ technologies: tags: - networking - security +redirect_from: +- guides/azure-kubernetes-service/cni-overlay-nsg/index.md --- # ✅ AKS CNI Overlay 사용 시 NSG 설정 주의사항 diff --git a/docs/cases/azure-kubernetes-service/file-io-throttling/index.md b/docs/services/azure-kubernetes-service/file-io-throttling/index.md similarity index 97% rename from docs/cases/azure-kubernetes-service/file-io-throttling/index.md rename to docs/services/azure-kubernetes-service/file-io-throttling/index.md index ad2c273..73e0625 100644 --- a/docs/cases/azure-kubernetes-service/file-io-throttling/index.md +++ b/docs/services/azure-kubernetes-service/file-io-throttling/index.md @@ -17,6 +17,8 @@ tags: - performance - troubleshooting occurred_at: 2025-11-05 +redirect_from: +- cases/azure-kubernetes-service/file-io-throttling/index.md --- # AKS Pod File I/O Throttling 분석 diff --git a/docs/guides/azure-kubernetes-service/kaito-open-source-model/index.md b/docs/services/azure-kubernetes-service/kaito-open-source-model/index.md similarity index 99% rename from docs/guides/azure-kubernetes-service/kaito-open-source-model/index.md rename to docs/services/azure-kubernetes-service/kaito-open-source-model/index.md index 70e8361..fc16444 100644 --- a/docs/guides/azure-kubernetes-service/kaito-open-source-model/index.md +++ b/docs/services/azure-kubernetes-service/kaito-open-source-model/index.md @@ -19,6 +19,8 @@ technologies: tags: - ai-agents - deployment +redirect_from: +- guides/azure-kubernetes-service/kaito-open-source-model/index.md --- # AKS + KAITO로 오픈소스 LLM 서빙하기 diff --git a/docs/cases/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md b/docs/services/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md similarity index 99% rename from docs/cases/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md rename to docs/services/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md index 3fadfbf..d98be16 100644 --- a/docs/cases/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md +++ b/docs/services/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md @@ -17,6 +17,8 @@ tags: - performance - troubleshooting occurred_at: 2025-11-07 +redirect_from: +- cases/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md --- # AKS NetApp Files 환경에서의 CPU 급증 및 File I/O 대기 이슈 diff --git a/docs/guides/azure-kubernetes-service/pod-affinity-distribution/index.md b/docs/services/azure-kubernetes-service/pod-affinity-distribution/index.md similarity index 97% rename from docs/guides/azure-kubernetes-service/pod-affinity-distribution/index.md rename to docs/services/azure-kubernetes-service/pod-affinity-distribution/index.md index 4e37e14..7772c14 100644 --- a/docs/guides/azure-kubernetes-service/pod-affinity-distribution/index.md +++ b/docs/services/azure-kubernetes-service/pod-affinity-distribution/index.md @@ -19,6 +19,8 @@ technologies: tags: - kubernetes - reliability +redirect_from: +- guides/azure-kubernetes-service/pod-affinity-distribution/index.md --- # ✅ AKS에서 Pod 분산 예제 diff --git a/docs/cases/azure-kubernetes-service/pod-database-query-latency/images/01_query_latency.png b/docs/services/azure-kubernetes-service/pod-database-query-latency/images/01_query_latency.png similarity index 100% rename from docs/cases/azure-kubernetes-service/pod-database-query-latency/images/01_query_latency.png rename to docs/services/azure-kubernetes-service/pod-database-query-latency/images/01_query_latency.png diff --git a/docs/cases/azure-kubernetes-service/pod-database-query-latency/images/02_pod_runtime.png b/docs/services/azure-kubernetes-service/pod-database-query-latency/images/02_pod_runtime.png similarity index 100% rename from docs/cases/azure-kubernetes-service/pod-database-query-latency/images/02_pod_runtime.png rename to docs/services/azure-kubernetes-service/pod-database-query-latency/images/02_pod_runtime.png diff --git a/docs/cases/azure-kubernetes-service/pod-database-query-latency/images/pod_db_query_latency.png b/docs/services/azure-kubernetes-service/pod-database-query-latency/images/pod_db_query_latency.png similarity index 100% rename from docs/cases/azure-kubernetes-service/pod-database-query-latency/images/pod_db_query_latency.png rename to docs/services/azure-kubernetes-service/pod-database-query-latency/images/pod_db_query_latency.png diff --git a/docs/cases/azure-kubernetes-service/pod-database-query-latency/images/result_01.png b/docs/services/azure-kubernetes-service/pod-database-query-latency/images/result_01.png similarity index 100% rename from docs/cases/azure-kubernetes-service/pod-database-query-latency/images/result_01.png rename to docs/services/azure-kubernetes-service/pod-database-query-latency/images/result_01.png diff --git a/docs/cases/azure-kubernetes-service/pod-database-query-latency/images/result_02.png b/docs/services/azure-kubernetes-service/pod-database-query-latency/images/result_02.png similarity index 100% rename from docs/cases/azure-kubernetes-service/pod-database-query-latency/images/result_02.png rename to docs/services/azure-kubernetes-service/pod-database-query-latency/images/result_02.png diff --git a/docs/cases/azure-kubernetes-service/pod-database-query-latency/images/result_pod_runtime.png b/docs/services/azure-kubernetes-service/pod-database-query-latency/images/result_pod_runtime.png similarity index 100% rename from docs/cases/azure-kubernetes-service/pod-database-query-latency/images/result_pod_runtime.png rename to docs/services/azure-kubernetes-service/pod-database-query-latency/images/result_pod_runtime.png diff --git a/docs/cases/azure-kubernetes-service/pod-database-query-latency/index.md b/docs/services/azure-kubernetes-service/pod-database-query-latency/index.md similarity index 99% rename from docs/cases/azure-kubernetes-service/pod-database-query-latency/index.md rename to docs/services/azure-kubernetes-service/pod-database-query-latency/index.md index 9c9e17a..72138f8 100644 --- a/docs/cases/azure-kubernetes-service/pod-database-query-latency/index.md +++ b/docs/services/azure-kubernetes-service/pod-database-query-latency/index.md @@ -19,6 +19,8 @@ tags: - networking - troubleshooting occurred_at: 2026-06-08 +redirect_from: +- cases/azure-kubernetes-service/pod-database-query-latency/index.md --- # AKS Pod → Database 쿼리 지연 디버깅 diff --git a/samples/azure-kubernetes-service/source-material/pod_db_query_latency/01_query_latency.png b/docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/01_query_latency.png similarity index 100% rename from samples/azure-kubernetes-service/source-material/pod_db_query_latency/01_query_latency.png rename to docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/01_query_latency.png diff --git a/samples/azure-kubernetes-service/source-material/pod_db_query_latency/02_pod_runtime.png b/docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/02_pod_runtime.png similarity index 100% rename from samples/azure-kubernetes-service/source-material/pod_db_query_latency/02_pod_runtime.png rename to docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/02_pod_runtime.png diff --git a/docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/README.md b/docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/README.md new file mode 100644 index 0000000..cbb8c78 --- /dev/null +++ b/docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/README.md @@ -0,0 +1,4 @@ +# Pod database query latency results + +These diagrams and captured results preserve the observations referenced by +the database query latency case. diff --git a/samples/azure-kubernetes-service/source-material/pod_db_query_latency/pod_db_query_latency.excalidraw b/docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/pod_db_query_latency.excalidraw similarity index 100% rename from samples/azure-kubernetes-service/source-material/pod_db_query_latency/pod_db_query_latency.excalidraw rename to docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/pod_db_query_latency.excalidraw diff --git a/samples/azure-kubernetes-service/source-material/pod_db_query_latency/pod_db_query_latency.png b/docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/pod_db_query_latency.png similarity index 100% rename from samples/azure-kubernetes-service/source-material/pod_db_query_latency/pod_db_query_latency.png rename to docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/pod_db_query_latency.png diff --git a/samples/azure-kubernetes-service/source-material/pod_db_query_latency/result_01.png b/docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/result_01.png similarity index 100% rename from samples/azure-kubernetes-service/source-material/pod_db_query_latency/result_01.png rename to docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/result_01.png diff --git a/samples/azure-kubernetes-service/source-material/pod_db_query_latency/result_02.png b/docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/result_02.png similarity index 100% rename from samples/azure-kubernetes-service/source-material/pod_db_query_latency/result_02.png rename to docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/result_02.png diff --git a/samples/azure-kubernetes-service/source-material/pod_db_query_latency/result_pod_runtime.png b/docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/result_pod_runtime.png similarity index 100% rename from samples/azure-kubernetes-service/source-material/pod_db_query_latency/result_pod_runtime.png rename to docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/result_pod_runtime.png diff --git a/docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/sample.yml b/docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/sample.yml new file mode 100644 index 0000000..6267ad6 --- /dev/null +++ b/docs/services/azure-kubernetes-service/pod-database-query-latency/samples/observed-results/sample.yml @@ -0,0 +1,5 @@ +title: Pod database query latency results +description: Captured diagrams and measurements from the investigated scenario. +kind: artifact +used_by: +- index diff --git a/docs/guides/azure-kubernetes-service/pod-scheduling-agent-pools/index.md b/docs/services/azure-kubernetes-service/pod-scheduling-agent-pools/index.md similarity index 97% rename from docs/guides/azure-kubernetes-service/pod-scheduling-agent-pools/index.md rename to docs/services/azure-kubernetes-service/pod-scheduling-agent-pools/index.md index 9efd8e3..3ea0b7e 100644 --- a/docs/guides/azure-kubernetes-service/pod-scheduling-agent-pools/index.md +++ b/docs/services/azure-kubernetes-service/pod-scheduling-agent-pools/index.md @@ -19,6 +19,8 @@ technologies: tags: - kubernetes - deployment +redirect_from: +- guides/azure-kubernetes-service/pod-scheduling-agent-pools/index.md --- # ✅ AKS에서 Pod 스케줄링 제어 Best Practice diff --git a/docs/guides/azure-kubernetes-service/pyroscope-anf-s3/images/anf-create-bucket.png b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/images/anf-create-bucket.png similarity index 100% rename from docs/guides/azure-kubernetes-service/pyroscope-anf-s3/images/anf-create-bucket.png rename to docs/services/azure-kubernetes-service/pyroscope-anf-s3/images/anf-create-bucket.png diff --git a/docs/guides/azure-kubernetes-service/pyroscope-anf-s3/images/anf-create-certificate.png b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/images/anf-create-certificate.png similarity index 100% rename from docs/guides/azure-kubernetes-service/pyroscope-anf-s3/images/anf-create-certificate.png rename to docs/services/azure-kubernetes-service/pyroscope-anf-s3/images/anf-create-certificate.png diff --git a/docs/guides/azure-kubernetes-service/pyroscope-anf-s3/images/architecture.png b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/images/architecture.png similarity index 100% rename from docs/guides/azure-kubernetes-service/pyroscope-anf-s3/images/architecture.png rename to docs/services/azure-kubernetes-service/pyroscope-anf-s3/images/architecture.png diff --git a/docs/guides/azure-kubernetes-service/pyroscope-anf-s3/images/pyroscope_ui.png b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/images/pyroscope_ui.png similarity index 100% rename from docs/guides/azure-kubernetes-service/pyroscope-anf-s3/images/pyroscope_ui.png rename to docs/services/azure-kubernetes-service/pyroscope-anf-s3/images/pyroscope_ui.png diff --git a/docs/guides/azure-kubernetes-service/pyroscope-anf-s3/index.md b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md similarity index 96% rename from docs/guides/azure-kubernetes-service/pyroscope-anf-s3/index.md rename to docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md index 9c943a7..61d0b13 100644 --- a/docs/guides/azure-kubernetes-service/pyroscope-anf-s3/index.md +++ b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md @@ -21,6 +21,8 @@ tags: - observability - deployment - storage +redirect_from: +- guides/azure-kubernetes-service/pyroscope-anf-s3/index.md --- # Pyroscope을 AKS에 배포하기 (S3 백엔드를 Azure NetApp Files로) @@ -403,8 +405,8 @@ kubectl port-forward -n observability svc/pyroscope 4040:4040 아래는 동작을 바로 확인할 수 있는 **데모**입니다. Azure AI Foundry를 호출하는 FastAPI 앱에 Pyroscope Python SDK를 붙여 콜스택을 수집합니다. -> 예제 코드: [pyroscope-chat-profiling/](https://github.com/hellices/devguidesample/tree/main/samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling) -> ([app.py](https://github.com/hellices/devguidesample/blob/main/samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/app.py) · [deployment.yaml](https://github.com/hellices/devguidesample/blob/main/samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/deployment.yaml) · [deploy.sh](https://github.com/hellices/devguidesample/blob/main/samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/deploy.sh) · [test.py](https://github.com/hellices/devguidesample/blob/main/samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/test.py)) +> 예제 코드: [pyroscope-chat-profiling/](https://github.com/hellices/devguidesample/tree/main/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling) +> ([app.py](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/app.py) · [deployment.yaml](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/deployment.yaml) · [deploy.sh](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/deploy.sh) · [test.py](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/test.py))
5.1 핵심 동작 (펼치기) diff --git a/samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/Dockerfile b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/Dockerfile similarity index 100% rename from samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/Dockerfile rename to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/Dockerfile diff --git a/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/README.md b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/README.md new file mode 100644 index 0000000..e454d0e --- /dev/null +++ b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/README.md @@ -0,0 +1,4 @@ +# Pyroscope chat profiling + +This runnable sample contains the application, deployment assets, test client, +and architecture images for the Pyroscope and Azure NetApp Files scenario. diff --git a/samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/app.py b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/app.py similarity index 100% rename from samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/app.py rename to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/app.py diff --git a/samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/deploy.sh b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/deploy.sh similarity index 100% rename from samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/deploy.sh rename to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/deploy.sh diff --git a/samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/deployment.yaml b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/deployment.yaml similarity index 100% rename from samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/deployment.yaml rename to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/deployment.yaml diff --git a/samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/images/anf-create-bucket.png b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/images/anf-create-bucket.png similarity index 100% rename from samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/images/anf-create-bucket.png rename to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/images/anf-create-bucket.png diff --git a/samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/images/anf-create-certificate.png b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/images/anf-create-certificate.png similarity index 100% rename from samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/images/anf-create-certificate.png rename to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/images/anf-create-certificate.png diff --git a/samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/images/architecture.excalidraw b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/images/architecture.excalidraw similarity index 100% rename from samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/images/architecture.excalidraw rename to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/images/architecture.excalidraw diff --git a/samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/images/architecture.png b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/images/architecture.png similarity index 100% rename from samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/images/architecture.png rename to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/images/architecture.png diff --git a/samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/images/pyroscope_ui.png b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/images/pyroscope_ui.png similarity index 100% rename from samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/images/pyroscope_ui.png rename to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/images/pyroscope_ui.png diff --git a/samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/requirements.txt b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/requirements.txt similarity index 100% rename from samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/requirements.txt rename to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/requirements.txt diff --git a/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/sample.yml b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/sample.yml new file mode 100644 index 0000000..2850287 --- /dev/null +++ b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/sample.yml @@ -0,0 +1,5 @@ +title: Pyroscope chat profiling +description: Runnable profiling workload and deployment assets. +kind: runnable +used_by: +- index diff --git a/samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/test.py b/docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/test.py similarity index 100% rename from samples/azure-kubernetes-service/source-material/pyroscope-chat-profiling/test.py rename to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/test.py diff --git a/docs/guides/azure-kubernetes-service/python-memory-leak-memray/images/leak-leaks.png b/docs/services/azure-kubernetes-service/python-memory-leak-memray/images/leak-leaks.png similarity index 100% rename from docs/guides/azure-kubernetes-service/python-memory-leak-memray/images/leak-leaks.png rename to docs/services/azure-kubernetes-service/python-memory-leak-memray/images/leak-leaks.png diff --git a/docs/guides/azure-kubernetes-service/python-memory-leak-memray/index.md b/docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md similarity index 93% rename from docs/guides/azure-kubernetes-service/python-memory-leak-memray/index.md rename to docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md index 103ed0a..3880838 100644 --- a/docs/guides/azure-kubernetes-service/python-memory-leak-memray/index.md +++ b/docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md @@ -21,6 +21,8 @@ tags: - diagnostics - performance - troubleshooting +redirect_from: +- guides/azure-kubernetes-service/python-memory-leak-memray/index.md --- # AKS Python Pod 메모리 누수 — `kubectl debug` + Memray attach @@ -46,7 +48,7 @@ tags: ## 사전 준비: 디버그 이미지 -gdb + procps + memray를 담은 이미지를 레지스트리에 상비 ([debug/Dockerfile](https://github.com/hellices/devguidesample/blob/main/samples/azure-kubernetes-service/source-material/memray-leak-profiling/debug/Dockerfile)): +gdb + procps + memray를 담은 이미지를 레지스트리에 상비 ([debug/Dockerfile](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/debug/Dockerfile)): ```dockerfile # ⚠️ 베이스는 앱과 동일 Python 버전 — memray attach 는 "타깃 프로세스 안"에서 @@ -170,7 +172,7 @@ kubectl cp memray-demo/$POD:/tmp/leak-leaks.html reports/leak-leaks.html ![memray --leaks 플레임그래프: chat → _remember_turn_context (app.py:50) 와 _archive_tool_trace (app.py:65) 가 미해제 메모리의 대부분을 차지](images/leak-leaks.png) -- 원본 HTML 파일(내려받아 브라우저에서 열기): [`reports/leak-leaks.html`](https://github.com/hellices/devguidesample/blob/main/samples/azure-kubernetes-service/source-material/memray-leak-profiling/reports/leak-leaks.html) · [`reports/leak-flamegraph.html`](https://github.com/hellices/devguidesample/blob/main/samples/azure-kubernetes-service/source-material/memray-leak-profiling/reports/leak-flamegraph.html) +- 원본 HTML 파일(내려받아 브라우저에서 열기): [`reports/leak-leaks.html`](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-leaks.html) · [`reports/leak-flamegraph.html`](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-flamegraph.html) - 절대 수치는 캡처 시점/부하에 따라 상이 (tree와 스크린샷은 별도 60s 캡처) — 핵심은 **비율 + 콜스택** - 대조군 `_synthesize_answer`(요청 종료 시 해제)는 leaks 리포트 **미등장** → 오탐 없음 - 상단 노란 배너 = pymalloc 안내: 소형 객체는 풀 잔류 노이즈 가능 → @@ -210,7 +212,7 @@ kubectl rollout restart deploy/leaky-agent -n memray-demo ## Appendix -재현 환경 전체 코드: [`memray-leak-profiling/`](https://github.com/hellices/devguidesample/tree/main/samples/azure-kubernetes-service/source-material/memray-leak-profiling) +재현 환경 전체 코드: [`memray-leak-profiling/`](https://github.com/hellices/devguidesample/tree/main/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling) ``` memray-leak-profiling/ @@ -240,7 +242,7 @@ memray-leak-profiling/ (agent 서비스에서 대화 메모리/툴 로그 무제한 축적 시 동일 증상) - `/stats`로 RSS/세션 수/히스토리 수 노출 → 관찰용 -앱 이미지는 clean 유지 ([Dockerfile](https://github.com/hellices/devguidesample/blob/main/samples/azure-kubernetes-service/source-material/memray-leak-profiling/Dockerfile)): +앱 이미지는 clean 유지 ([Dockerfile](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/Dockerfile)): ```dockerfile FROM python:3.12-slim @@ -269,7 +271,7 @@ az aks get-credentials -g rg-memray-demo -n aks-memray-demo 이미지 빌드 — 로컬 docker 불필요, ACR 클라우드 빌드 (Apple Silicon 크로스빌드 이슈도 회피): ```bash -cd samples/azure-kubernetes-service/source-material/memray-leak-profiling +cd docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling az acr build --registry --image leaky-agent:v2 --platform linux/amd64 . # 24s az acr build --registry --image memray-debug:v1 --platform linux/amd64 ./debug # 33s ``` diff --git a/samples/azure-kubernetes-service/source-material/memray-leak-profiling/Dockerfile b/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/Dockerfile similarity index 100% rename from samples/azure-kubernetes-service/source-material/memray-leak-profiling/Dockerfile rename to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/Dockerfile diff --git a/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/README.md b/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/README.md new file mode 100644 index 0000000..278c8ad --- /dev/null +++ b/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/README.md @@ -0,0 +1,4 @@ +# Memray leak profiling + +This runnable sample includes the leaky application, load generator, debug +image, Kubernetes manifests, and captured Memray reports. diff --git a/samples/azure-kubernetes-service/source-material/memray-leak-profiling/app.py b/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/app.py similarity index 100% rename from samples/azure-kubernetes-service/source-material/memray-leak-profiling/app.py rename to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/app.py diff --git a/samples/azure-kubernetes-service/source-material/memray-leak-profiling/debug/Dockerfile b/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/debug/Dockerfile similarity index 100% rename from samples/azure-kubernetes-service/source-material/memray-leak-profiling/debug/Dockerfile rename to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/debug/Dockerfile diff --git a/samples/azure-kubernetes-service/source-material/memray-leak-profiling/k8s/leaky-agent.yaml b/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/k8s/leaky-agent.yaml similarity index 100% rename from samples/azure-kubernetes-service/source-material/memray-leak-profiling/k8s/leaky-agent.yaml rename to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/k8s/leaky-agent.yaml diff --git a/samples/azure-kubernetes-service/source-material/memray-leak-profiling/k8s/loadgen.yaml b/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/k8s/loadgen.yaml similarity index 100% rename from samples/azure-kubernetes-service/source-material/memray-leak-profiling/k8s/loadgen.yaml rename to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/k8s/loadgen.yaml diff --git a/samples/azure-kubernetes-service/source-material/memray-leak-profiling/loadgen.py b/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/loadgen.py similarity index 100% rename from samples/azure-kubernetes-service/source-material/memray-leak-profiling/loadgen.py rename to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/loadgen.py diff --git a/samples/azure-kubernetes-service/source-material/memray-leak-profiling/reports/leak-flamegraph.html b/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-flamegraph.html similarity index 100% rename from samples/azure-kubernetes-service/source-material/memray-leak-profiling/reports/leak-flamegraph.html rename to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-flamegraph.html diff --git a/samples/azure-kubernetes-service/source-material/memray-leak-profiling/reports/leak-leaks.html b/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-leaks.html similarity index 100% rename from samples/azure-kubernetes-service/source-material/memray-leak-profiling/reports/leak-leaks.html rename to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-leaks.html diff --git a/samples/azure-kubernetes-service/source-material/memray-leak-profiling/reports/leak-leaks.png b/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-leaks.png similarity index 100% rename from samples/azure-kubernetes-service/source-material/memray-leak-profiling/reports/leak-leaks.png rename to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-leaks.png diff --git a/samples/azure-kubernetes-service/source-material/memray-leak-profiling/requirements.txt b/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/requirements.txt similarity index 100% rename from samples/azure-kubernetes-service/source-material/memray-leak-profiling/requirements.txt rename to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/requirements.txt diff --git a/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/sample.yml b/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/sample.yml new file mode 100644 index 0000000..a2393ee --- /dev/null +++ b/docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/sample.yml @@ -0,0 +1,5 @@ +title: Memray leak profiling +description: Runnable Python memory-leak workload with profiling reports. +kind: runnable +used_by: +- index diff --git a/docs/guides/azure-kubernetes-service/remote-cluster-local-development/index.md b/docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md similarity index 87% rename from docs/guides/azure-kubernetes-service/remote-cluster-local-development/index.md rename to docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md index a9bf932..da856ba 100644 --- a/docs/guides/azure-kubernetes-service/remote-cluster-local-development/index.md +++ b/docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md @@ -20,6 +20,8 @@ technologies: tags: - development - networking +redirect_from: +- guides/azure-kubernetes-service/remote-cluster-local-development/index.md --- # AKS 원격 클러스터 + 로컬 개발 루프 (Telepresence / mirrord) @@ -36,10 +38,10 @@ AKS에 배포된 서비스를 대상으로, **코드를 이미지로 빌드/배 - [Use Telepresence to develop and test microservices locally — MS Learn](https://learn.microsoft.com/en-us/azure/aks/use-telepresence-aks) - [Local Development on AKS with mirrord — AKS Engineering Blog](https://blog.aks.azure.com/2024/12/04/mirrord-on-aks) -예제 파일: [`local_dev_loop/`](https://github.com/hellices/devguidesample/tree/main/samples/azure-kubernetes-service/source-material/local_dev_loop) +예제 파일: [`local-development-loop/`](https://github.com/hellices/devguidesample/tree/main/docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop) ``` -local_dev_loop/ +local-development-loop/ ├── k8s/echo-service.yaml # tp-demo 네임스페이스 + echo-server Deployment/Service └── local/ ├── local-server.py # 트래픽을 가로챌 로컬 개발 서버 (:8080) @@ -61,7 +63,7 @@ local_dev_loop/ ```bash az aks get-credentials -g rg-example-koreacentral-01 -n aks-example-koreacentral-01 -kubectl apply -f samples/azure-kubernetes-service/source-material/local_dev_loop/k8s/echo-service.yaml +kubectl apply -f docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/k8s/echo-service.yaml kubectl -n tp-demo rollout status deploy/echo-server ``` @@ -104,7 +106,7 @@ curl http://echo-server.tp-demo # 클러스터 Pod가 응답 telepresence list # inbound intercept: 클러스터 트래픽을 로컬로 -python3 local_dev_loop/local/local-server.py & +python3 docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/local/local-server.py & telepresence intercept echo-server --namespace tp-demo --port 8080:80 # --env-file을 주면 원격 Pod의 환경변수를 파일로 받아 로컬 앱에서 재사용 가능 @@ -150,7 +152,7 @@ syscall 수준에서 네트워크/환경변수/파일을 원격 Pod의 것으로 ### 3-2. 설정 파일 ```jsonc -// local_dev_loop/local/mirrord.json +// docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/local/mirrord.json { "target": { "path": "deployment/echo-server", "namespace": "tp-demo" }, "feature": { @@ -176,15 +178,15 @@ syscall 수준에서 네트워크/환경변수/파일을 원격 Pod의 것으로 ```bash # 인바운드 steal + env/dns/fs 미러링 -mirrord exec -f local_dev_loop/local/mirrord.json -- \ - python3 local_dev_loop/local/local-server.py +mirrord exec -f docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/local/mirrord.json -- \ + python3 docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/local/local-server.py # 로컬 프로세스가 원격 Pod의 환경변수를 그대로 받음 -mirrord exec -f local_dev_loop/local/mirrord.json -- \ +mirrord exec -f docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/local/mirrord.json -- \ python3 -c "import os; print(os.environ['POD_NAME'])" # 아웃바운드: 클러스터 내부 DNS도 프로세스 안에서 해석됨 -mirrord exec -f local_dev_loop/local/mirrord.json -- \ +mirrord exec -f docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/local/mirrord.json -- \ curl http://echo-server.tp-demo.svc.cluster.local ``` @@ -204,7 +206,7 @@ F5(Run/Debug)만으로 mirrord가 적용된다. **브레이크포인트를 찍 "name": "mirrord demo: local-server (echo-server steal)", "type": "debugpy", "request": "launch", - "program": "${workspaceFolder}/samples/azure-kubernetes-service/source-material/local_dev_loop/local/local-server.py", + "program": "${workspaceFolder}/docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/local/local-server.py", "console": "integratedTerminal", "env": { "MIRRORD_ACTIVE": "1", @@ -258,7 +260,7 @@ telepresence helm uninstall # Traffic Manager 제거 # mirrord — agent pod는 세션 종료 시 자동 삭제됨 # 샘플 서비스 제거 -kubectl delete -f samples/azure-kubernetes-service/source-material/local_dev_loop/k8s/echo-service.yaml +kubectl delete -f docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/k8s/echo-service.yaml # 비용 절약: 클러스터 중지 az aks stop -g rg-example-koreacentral-01 -n aks-example-koreacentral-01 diff --git a/docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/README.md b/docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/README.md new file mode 100644 index 0000000..8668f5f --- /dev/null +++ b/docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/README.md @@ -0,0 +1,4 @@ +# Local development loop + +This runnable sample provides the remote Kubernetes workload, local Python +server, and mirrord configuration used by the development loop. diff --git a/samples/azure-kubernetes-service/source-material/local_dev_loop/k8s/echo-service.yaml b/docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/k8s/echo-service.yaml similarity index 100% rename from samples/azure-kubernetes-service/source-material/local_dev_loop/k8s/echo-service.yaml rename to docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/k8s/echo-service.yaml diff --git a/samples/azure-kubernetes-service/source-material/local_dev_loop/local/local-server.py b/docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/local/local-server.py similarity index 100% rename from samples/azure-kubernetes-service/source-material/local_dev_loop/local/local-server.py rename to docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/local/local-server.py diff --git a/samples/azure-kubernetes-service/source-material/local_dev_loop/local/mirrord.json b/docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/local/mirrord.json similarity index 100% rename from samples/azure-kubernetes-service/source-material/local_dev_loop/local/mirrord.json rename to docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/local/mirrord.json diff --git a/docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/sample.yml b/docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/sample.yml new file mode 100644 index 0000000..7d75a6b --- /dev/null +++ b/docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/sample.yml @@ -0,0 +1,5 @@ +title: Local development loop +description: Runnable remote-cluster and local-process development example. +kind: runnable +used_by: +- index diff --git a/docs/guides/azure-kubernetes-service/spot-h100-kaito/index.md b/docs/services/azure-kubernetes-service/spot-h100-kaito/index.md similarity index 99% rename from docs/guides/azure-kubernetes-service/spot-h100-kaito/index.md rename to docs/services/azure-kubernetes-service/spot-h100-kaito/index.md index 3828c5e..aff2911 100644 --- a/docs/guides/azure-kubernetes-service/spot-h100-kaito/index.md +++ b/docs/services/azure-kubernetes-service/spot-h100-kaito/index.md @@ -21,6 +21,8 @@ tags: - ai-agents - deployment - performance +redirect_from: +- guides/azure-kubernetes-service/spot-h100-kaito/index.md --- # ✅ AKS Spot H100 GPU + KAITO LLM 서빙 가이드 diff --git a/docs/guides/azure-kubernetes-service/workload-identity-databricks/index.md b/docs/services/azure-kubernetes-service/workload-identity-databricks/index.md similarity index 99% rename from docs/guides/azure-kubernetes-service/workload-identity-databricks/index.md rename to docs/services/azure-kubernetes-service/workload-identity-databricks/index.md index 2871c68..304667b 100644 --- a/docs/guides/azure-kubernetes-service/workload-identity-databricks/index.md +++ b/docs/services/azure-kubernetes-service/workload-identity-databricks/index.md @@ -22,6 +22,8 @@ technologies: tags: - authentication - security +redirect_from: +- guides/azure-kubernetes-service/workload-identity-databricks/index.md --- # AKS Workload Identity로 Databricks Model Serving 키리스(Keyless) 호출 diff --git a/docs/guides/azure-load-testing/locust-appgw-aks-private/images/01-test-list.png b/docs/services/azure-load-testing/locust-appgw-aks-private/images/01-test-list.png similarity index 100% rename from docs/guides/azure-load-testing/locust-appgw-aks-private/images/01-test-list.png rename to docs/services/azure-load-testing/locust-appgw-aks-private/images/01-test-list.png diff --git a/docs/guides/azure-load-testing/locust-appgw-aks-private/images/02-run-summary.png b/docs/services/azure-load-testing/locust-appgw-aks-private/images/02-run-summary.png similarity index 100% rename from docs/guides/azure-load-testing/locust-appgw-aks-private/images/02-run-summary.png rename to docs/services/azure-load-testing/locust-appgw-aks-private/images/02-run-summary.png diff --git a/docs/guides/azure-load-testing/locust-appgw-aks-private/images/03-request-statistics.png b/docs/services/azure-load-testing/locust-appgw-aks-private/images/03-request-statistics.png similarity index 100% rename from docs/guides/azure-load-testing/locust-appgw-aks-private/images/03-request-statistics.png rename to docs/services/azure-load-testing/locust-appgw-aks-private/images/03-request-statistics.png diff --git a/docs/guides/azure-load-testing/locust-appgw-aks-private/images/04-client-side-metrics.png b/docs/services/azure-load-testing/locust-appgw-aks-private/images/04-client-side-metrics.png similarity index 100% rename from docs/guides/azure-load-testing/locust-appgw-aks-private/images/04-client-side-metrics.png rename to docs/services/azure-load-testing/locust-appgw-aks-private/images/04-client-side-metrics.png diff --git a/docs/guides/azure-load-testing/locust-appgw-aks-private/images/05-server-side-metrics.png b/docs/services/azure-load-testing/locust-appgw-aks-private/images/05-server-side-metrics.png similarity index 100% rename from docs/guides/azure-load-testing/locust-appgw-aks-private/images/05-server-side-metrics.png rename to docs/services/azure-load-testing/locust-appgw-aks-private/images/05-server-side-metrics.png diff --git a/docs/guides/azure-load-testing/locust-appgw-aks-private/images/06-engine-health.png b/docs/services/azure-load-testing/locust-appgw-aks-private/images/06-engine-health.png similarity index 100% rename from docs/guides/azure-load-testing/locust-appgw-aks-private/images/06-engine-health.png rename to docs/services/azure-load-testing/locust-appgw-aks-private/images/06-engine-health.png diff --git a/docs/guides/azure-load-testing/locust-appgw-aks-private/images/07-compare-select.png b/docs/services/azure-load-testing/locust-appgw-aks-private/images/07-compare-select.png similarity index 100% rename from docs/guides/azure-load-testing/locust-appgw-aks-private/images/07-compare-select.png rename to docs/services/azure-load-testing/locust-appgw-aks-private/images/07-compare-select.png diff --git a/docs/guides/azure-load-testing/locust-appgw-aks-private/images/08-compare-result.png b/docs/services/azure-load-testing/locust-appgw-aks-private/images/08-compare-result.png similarity index 100% rename from docs/guides/azure-load-testing/locust-appgw-aks-private/images/08-compare-result.png rename to docs/services/azure-load-testing/locust-appgw-aks-private/images/08-compare-result.png diff --git a/docs/guides/azure-load-testing/locust-appgw-aks-private/index.md b/docs/services/azure-load-testing/locust-appgw-aks-private/index.md similarity index 99% rename from docs/guides/azure-load-testing/locust-appgw-aks-private/index.md rename to docs/services/azure-load-testing/locust-appgw-aks-private/index.md index 802a271..f1b560b 100644 --- a/docs/guides/azure-load-testing/locust-appgw-aks-private/index.md +++ b/docs/services/azure-load-testing/locust-appgw-aks-private/index.md @@ -21,6 +21,8 @@ tags: - benchmarking - networking - performance +redirect_from: +- guides/azure-load-testing/locust-appgw-aks-private/index.md --- # Korea Central Private 환경의 Locust 부하테스트 구성 가이드 diff --git a/docs/cases/azure-managed-redis/cluster-failover-recovery/index.md b/docs/services/azure-managed-redis/cluster-failover-recovery/index.md similarity index 97% rename from docs/cases/azure-managed-redis/cluster-failover-recovery/index.md rename to docs/services/azure-managed-redis/cluster-failover-recovery/index.md index 208a9bc..9d2d010 100644 --- a/docs/cases/azure-managed-redis/cluster-failover-recovery/index.md +++ b/docs/services/azure-managed-redis/cluster-failover-recovery/index.md @@ -18,6 +18,8 @@ tags: - high-availability - troubleshooting occurred_at: 2026-02-22 +redirect_from: +- cases/azure-managed-redis/cluster-failover-recovery/index.md --- # Azure Managed Redis Cluster Failover 장애 분석 및 복구 전략 @@ -237,7 +239,7 @@ async function safeRedisGet(key) { ## 코드 샘플 -이 이슈를 검증하기 위한 예제 코드는 [`cluster_example/`](https://github.com/hellices/devguidesample/tree/main/samples/azure-managed-redis/source-material/nodejs/cluster_example) 디렉토리에 포함되어 있습니다. +이 이슈를 검증하기 위한 예제 코드는 [`cluster_example/`](https://github.com/hellices/devguidesample/tree/main/docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example) 디렉토리에 포함되어 있습니다. - OSS Cluster(`createCluster`) vs Enterprise(`createClient`) 연결 방식 비교 - Cluster topology 조회 및 모니터링 diff --git a/docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/README.md b/docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/README.md new file mode 100644 index 0000000..8e76746 --- /dev/null +++ b/docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/README.md @@ -0,0 +1,4 @@ +# Node.js cluster failover sample + +This runnable sample contains the Node.js cluster client and editable recovery +diagram used to verify failover behavior. diff --git a/samples/azure-managed-redis/source-material/nodejs/cluster_example/README.md b/docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/README.md similarity index 100% rename from samples/azure-managed-redis/source-material/nodejs/cluster_example/README.md rename to docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/README.md diff --git a/samples/azure-managed-redis/source-material/nodejs/cluster_example/healthCheck.js b/docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/healthCheck.js similarity index 100% rename from samples/azure-managed-redis/source-material/nodejs/cluster_example/healthCheck.js rename to docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/healthCheck.js diff --git a/samples/azure-managed-redis/source-material/nodejs/cluster_example/package-lock.json b/docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/package-lock.json similarity index 100% rename from samples/azure-managed-redis/source-material/nodejs/cluster_example/package-lock.json rename to docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/package-lock.json diff --git a/samples/azure-managed-redis/source-material/nodejs/cluster_example/package.json b/docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/package.json similarity index 100% rename from samples/azure-managed-redis/source-material/nodejs/cluster_example/package.json rename to docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/package.json diff --git a/samples/azure-managed-redis/source-material/nodejs/cluster_example/redisClient.js b/docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/redisClient.js similarity index 100% rename from samples/azure-managed-redis/source-material/nodejs/cluster_example/redisClient.js rename to docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/redisClient.js diff --git a/samples/azure-managed-redis/source-material/nodejs/cluster_example/routes.js b/docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/routes.js similarity index 100% rename from samples/azure-managed-redis/source-material/nodejs/cluster_example/routes.js rename to docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/routes.js diff --git a/samples/azure-managed-redis/source-material/nodejs/cluster_example/server.js b/docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/server.js similarity index 100% rename from samples/azure-managed-redis/source-material/nodejs/cluster_example/server.js rename to docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/server.js diff --git a/samples/azure-managed-redis/source-material/nodejs/cluster_failover_recovery.excalidraw b/docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_failover_recovery.excalidraw similarity index 100% rename from samples/azure-managed-redis/source-material/nodejs/cluster_failover_recovery.excalidraw rename to docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_failover_recovery.excalidraw diff --git a/docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/sample.yml b/docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/sample.yml new file mode 100644 index 0000000..add728b --- /dev/null +++ b/docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/sample.yml @@ -0,0 +1,5 @@ +title: Node.js cluster failover sample +description: Runnable Node.js cluster client and failover recovery assets. +kind: runnable +used_by: +- index diff --git a/docs/guides/azure-monitor/aks-private-opentelemetry/index.md b/docs/services/azure-monitor/aks-private-opentelemetry/index.md similarity index 99% rename from docs/guides/azure-monitor/aks-private-opentelemetry/index.md rename to docs/services/azure-monitor/aks-private-opentelemetry/index.md index 329d34d..eab8b8a 100644 --- a/docs/guides/azure-monitor/aks-private-opentelemetry/index.md +++ b/docs/services/azure-monitor/aks-private-opentelemetry/index.md @@ -21,6 +21,8 @@ tags: - observability - monitoring - networking +redirect_from: +- guides/azure-monitor/aks-private-opentelemetry/index.md --- # AKS 폐쇄망 환경 OpenTelemetry + Azure Monitor 메트릭 파이프라인 구축 diff --git a/docs/guides/azure-monitor/sre-agent-dynamic-thresholds/index.md b/docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md similarity index 97% rename from docs/guides/azure-monitor/sre-agent-dynamic-thresholds/index.md rename to docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md index ca7de8f..315b99a 100644 --- a/docs/guides/azure-monitor/sre-agent-dynamic-thresholds/index.md +++ b/docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md @@ -20,6 +20,9 @@ tags: - monitoring - ai-agents - architecture +topic_order: 9 +redirect_from: +- guides/azure-monitor/sre-agent-dynamic-thresholds/index.md --- # Azure Monitor Dynamic Thresholds와 SRE Agent 연계 설계 @@ -80,7 +83,7 @@ query는 `summarize` 결과가 하나 이상의 numeric series를 반환해야 - `ignoreDataBefore`: 정상 telemetry가 안정적으로 쌓이기 시작한 UTC - Action Group: 기존 `ag-sre-agent-event-lab` - Event path(기본): Dynamic alert → Azure Monitor incident platform 연결 → Review 모드 response plan → investigation. 현재 실습이 배포하는 표준 경로이며 Dynamic rule도 같은 경로를 그대로 쓴다. -- Event path(레거시 bridge): Dynamic alert → Action Group → Logic App managed identity → SRE HTTP Trigger → Review-mode investigation. 2026-08-12 실측에 쓰인 legacy 구성이고 기본 실습에는 배포하지 않는다([실측 검증 결과](../../../research/azure-monitor/sre-agent-validation-results/index.md)). +- Event path(레거시 bridge): Dynamic alert → Action Group → Logic App managed identity → SRE HTTP Trigger → Review-mode investigation. 2026-08-12 실측에 쓰인 legacy 구성이고 기본 실습에는 배포하지 않는다([실측 검증 결과](../validation-results/index.md)). 이는 시작점이지 모든 workload의 정답이 아니다. Preview Chart와 incident 결과를 보고 sensitivity와 failing periods를 조정한다. diff --git a/docs/labs/azure-monitor/sre-agent-event-lab/index.md b/docs/services/azure-monitor/azure-sre-agent/event-lab/index.md similarity index 92% rename from docs/labs/azure-monitor/sre-agent-event-lab/index.md rename to docs/services/azure-monitor/azure-sre-agent/event-lab/index.md index e23585e..b9811e3 100644 --- a/docs/labs/azure-monitor/sre-agent-event-lab/index.md +++ b/docs/services/azure-monitor/azure-sre-agent/event-lab/index.md @@ -22,11 +22,14 @@ tags: - ai-agents - monitoring - troubleshooting +topic_order: 1 +redirect_from: +- labs/azure-monitor/sre-agent-event-lab/index.md --- # Azure SRE Agent 이벤트 기반 장애 분석 실습 -Azure Container Apps에 장애를 세 번 주입하고, Azure Monitor 경고를 받은 Azure SRE Agent가 실제로 조사·결론까지 도달하는지 확인합니다. 제품 개요는 [Azure SRE Agent 소개](../../../guides/azure-monitor/azure-sre-agent-overview/index.md)를 먼저 읽어 주세요. +Azure Container Apps에 장애를 세 번 주입하고, Azure Monitor 경고를 받은 Azure SRE Agent가 실제로 조사·결론까지 도달하는지 확인합니다. 제품 개요는 [Azure SRE Agent 소개](../index.md)를 먼저 읽어 주세요. > ⚠️ 이 실습은 실제 Azure 리소스를 만들고 **과금**합니다. 끝나면 반드시 [정리](#_8) 절차로 지우세요. @@ -68,7 +71,7 @@ Azure SRE Agent는 이 실습이 만들지 않습니다. 미리 만들어 둔 Ag az login --use-device-code azd auth login -source ./samples/azure-monitor/source-material/sre-agent-event-lab/scripts/lab-env.sh +source ./docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/lab-env.sh ``` `lab-env.sh`는 `azd`가 게시한 배포 출력만 읽어 리소스 그룹·구독·Container App·Storage 범위 등을 현재 셸에 export하고, 값이 하나라도 없으면 `LAB_READY=0`으로 알려 줍니다. 이후 가이드의 명령은 이 값들을 그대로 사용하므로 단계마다 다시 조회하지 않습니다. 비밀 값은 읽지도 출력하지도 않습니다. @@ -88,7 +91,7 @@ source ./samples/azure-monitor/source-material/sre-agent-event-lab/scripts/lab-e 이 실습의 모든 명령은 아래에서 한 번만 진입하는 이 디렉터리를 기준으로 합니다. ```bash -cd samples/azure-monitor/source-material/sre-agent-event-lab +cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab ``` 로컬 검증만 먼저 해 보려면 다음을 실행합니다. `setup-venv.sh`는 `azd up`의 postprovision 단계에서도 실행되므로, 배포를 먼저 한 경우에는 이미 준비된 상태입니다. @@ -134,13 +137,13 @@ postprovision 단계가 실패하면 로컬 환경만 실패한 것입니다. `. ## Azure SRE Agent 설정 -포털에서만 할 수 있는 설정이 남아 있습니다. 저장소 연결, 지식 문서 업로드, **Azure Monitor incident platform** 연결, `Review` 모드 응답 계획, 역할 할당을 [에이전트 설정](../sre-agent-event-lab-setup/index.md)이 순서대로 안내합니다. +포털에서만 할 수 있는 설정이 남아 있습니다. 저장소 연결, 지식 문서 업로드, **Azure Monitor incident platform** 연결, `Review` 모드 응답 계획, 역할 할당을 [에이전트 설정](../setup/index.md)이 순서대로 안내합니다. -기본 실습에는 Logic App bridge를 배포하지 않습니다. 제품 표준 경로는 Azure Monitor를 incident platform으로 연결하는 것이고, 예전 실측에서 쓰던 Action Group + Logic App 인증 경로는 레거시 기록으로만 남아 있습니다([실측 검증 결과](../../../research/azure-monitor/sre-agent-validation-results/index.md)). +기본 실습에는 Logic App bridge를 배포하지 않습니다. 제품 표준 경로는 Azure Monitor를 incident platform으로 연결하는 것이고, 예전 실측에서 쓰던 Action Group + Logic App 인증 경로는 레거시 기록으로만 남아 있습니다([실측 검증 결과](../validation-results/index.md)). ## 정상 상태 확인과 승인 -장애를 주입하기 전에 정상 부하가 Application Insights까지 도달하는지 확인하고, 그 사실을 기록해야 S1이 열립니다. 텔레메트리가 없는 워크로드에 장애를 넣으면 주입한 장애와 원래부터 안 보이던 상태를 구별할 수 없습니다. 명령은 [에이전트 설정](../sre-agent-event-lab-setup/index.md)에 있습니다. +장애를 주입하기 전에 정상 부하가 Application Insights까지 도달하는지 확인하고, 그 사실을 기록해야 S1이 열립니다. 텔레메트리가 없는 워크로드에 장애를 넣으면 주입한 장애와 원래부터 안 보이던 상태를 구별할 수 없습니다. 명령은 [에이전트 설정](../setup/index.md)에 있습니다. ```bash python3 scripts/lab_state.py mark baseline_passed --evidence-dir "${EVIDENCE_DIR}" @@ -159,9 +162,9 @@ python3 scripts/lab_state.py acknowledge-agent | 시나리오 | 주입하는 장애 | 안내 문서 | |---|---|---| -| S1 | HTTP 500 응답 | [S1 — HTTP 500 장애](../sre-agent-scenario-http-500/index.md) | -| S2 | 주문 API 지연 | [S2 — 응답 지연](../sre-agent-scenario-latency/index.md) | -| S3 | Blob 읽기 권한 제거 | [S3 — Blob 권한 장애](../sre-agent-scenario-blob-permission/index.md) | +| S1 | HTTP 500 응답 | [S1 — HTTP 500 장애](../scenario-http-500/index.md) | +| S2 | 주문 API 지연 | [S2 — 응답 지연](../scenario-latency/index.md) | +| S3 | Blob 읽기 권한 제거 | [S3 — Blob 권한 장애](../scenario-blob-permission/index.md) | ## 결과 확인 @@ -169,7 +172,7 @@ python3 scripts/lab_state.py acknowledge-agent app/.venv/bin/python scripts/score.py --evidence-root evidence ``` -채점 기준, 사람이 채워야 하는 판정, 종합 판정 해석은 [결과 채점](../sre-agent-results/index.md)에 있습니다. +채점 기준, 사람이 채워야 하는 판정, 종합 판정 해석은 [결과 채점](../results/index.md)에 있습니다. ## 정리 @@ -208,11 +211,11 @@ az group delete --subscription "${SUBSCRIPTION_ID}" --name "${RESOURCE_GROUP}" - | 증상 | 확인할 곳 | |---|---| | 배포 후 앱이 응답하지 않음 | `azd env get-value AZURE_CONTAINER_APP_FQDN`으로 FQDN을 확인한 뒤 `/healthz` 호출 | -| Agent가 스레드를 만들지 않음 | [에이전트 설정](../sre-agent-event-lab-setup/index.md)의 incident platform·응답 계획 확인 | +| Agent가 스레드를 만들지 않음 | [에이전트 설정](../setup/index.md)의 incident platform·응답 계획 확인 | | 경고가 발생하지 않음 | 시나리오 문서의 "복구 확인" 절 | -| 채점이 `INCOMPLETE` | [결과 채점](../sre-agent-results/index.md)의 수동 판정 절 | +| 채점이 `INCOMPLETE` | [결과 채점](../results/index.md)의 수동 판정 절 | -정적 임계값 대신 Dynamic Threshold로 확장하는 설계는 [Dynamic Thresholds 확장 설계](../../../guides/azure-monitor/sre-agent-dynamic-thresholds/index.md)에 정리해 두었습니다. +정적 임계값 대신 Dynamic Threshold로 확장하는 설계는 [Dynamic Thresholds 확장 설계](../dynamic-thresholds/index.md)에 정리해 두었습니다. ## 공식 자료 diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/agent-reasoning-flow.svg b/docs/services/azure-monitor/azure-sre-agent/images/agent-reasoning-flow.svg similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/agent-reasoning-flow.svg rename to docs/services/azure-monitor/azure-sre-agent/images/agent-reasoning-flow.svg diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/azure-sre-agent-networking-vnet.png b/docs/services/azure-monitor/azure-sre-agent/images/azure-sre-agent-networking-vnet.png similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/azure-sre-agent-networking-vnet.png rename to docs/services/azure-monitor/azure-sre-agent/images/azure-sre-agent-networking-vnet.png diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/custom-skill-flow.svg b/docs/services/azure-monitor/azure-sre-agent/images/custom-skill-flow.svg similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/custom-skill-flow.svg rename to docs/services/azure-monitor/azure-sre-agent/images/custom-skill-flow.svg diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/diagnose-azure-services.svg b/docs/services/azure-monitor/azure-sre-agent/images/diagnose-azure-services.svg similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/diagnose-azure-services.svg rename to docs/services/azure-monitor/azure-sre-agent/images/diagnose-azure-services.svg diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/github-issue.png b/docs/services/azure-monitor/azure-sre-agent/images/github-issue.png similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/github-issue.png rename to docs/services/azure-monitor/azure-sre-agent/images/github-issue.png diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/incident-platform-flow.svg b/docs/services/azure-monitor/azure-sre-agent/images/incident-platform-flow.svg similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/incident-platform-flow.svg rename to docs/services/azure-monitor/azure-sre-agent/images/incident-platform-flow.svg diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/incident-response-flow.svg b/docs/services/azure-monitor/azure-sre-agent/images/incident-response-flow.svg similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/incident-response-flow.svg rename to docs/services/azure-monitor/azure-sre-agent/images/incident-response-flow.svg diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/knowledge-sources.svg b/docs/services/azure-monitor/azure-sre-agent/images/knowledge-sources.svg similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/knowledge-sources.svg rename to docs/services/azure-monitor/azure-sre-agent/images/knowledge-sources.svg diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/managed-connectors-icon-grid.png b/docs/services/azure-monitor/azure-sre-agent/images/managed-connectors-icon-grid.png similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/managed-connectors-icon-grid.png rename to docs/services/azure-monitor/azure-sre-agent/images/managed-connectors-icon-grid.png diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/memory-auto-learning.svg b/docs/services/azure-monitor/azure-sre-agent/images/memory-auto-learning.svg similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/memory-auto-learning.svg rename to docs/services/azure-monitor/azure-sre-agent/images/memory-auto-learning.svg diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/memory-unified-search.svg b/docs/services/azure-monitor/azure-sre-agent/images/memory-unified-search.svg similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/memory-unified-search.svg rename to docs/services/azure-monitor/azure-sre-agent/images/memory-unified-search.svg diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/notification-paths.svg b/docs/services/azure-monitor/azure-sre-agent/images/notification-paths.svg similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/notification-paths.svg rename to docs/services/azure-monitor/azure-sre-agent/images/notification-paths.svg diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/operations-hub-overview-tab.png b/docs/services/azure-monitor/azure-sre-agent/images/operations-hub-overview-tab.png similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/operations-hub-overview-tab.png rename to docs/services/azure-monitor/azure-sre-agent/images/operations-hub-overview-tab.png diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/permission-flow.svg b/docs/services/azure-monitor/azure-sre-agent/images/permission-flow.svg similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/permission-flow.svg rename to docs/services/azure-monitor/azure-sre-agent/images/permission-flow.svg diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/portal-sub-agent-canvas-full.png b/docs/services/azure-monitor/azure-sre-agent/images/portal-sub-agent-canvas-full.png similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/portal-sub-agent-canvas-full.png rename to docs/services/azure-monitor/azure-sre-agent/images/portal-sub-agent-canvas-full.png diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/root-cause-analysis.svg b/docs/services/azure-monitor/azure-sre-agent/images/root-cause-analysis.svg similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/root-cause-analysis.svg rename to docs/services/azure-monitor/azure-sre-agent/images/root-cause-analysis.svg diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/run-modes-comparison.svg b/docs/services/azure-monitor/azure-sre-agent/images/run-modes-comparison.svg similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/run-modes-comparison.svg rename to docs/services/azure-monitor/azure-sre-agent/images/run-modes-comparison.svg diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/s1-agent-conclusion.png b/docs/services/azure-monitor/azure-sre-agent/images/s1-agent-conclusion.png similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/s1-agent-conclusion.png rename to docs/services/azure-monitor/azure-sre-agent/images/s1-agent-conclusion.png diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/s1-email-preview.png b/docs/services/azure-monitor/azure-sre-agent/images/s1-email-preview.png similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/s1-email-preview.png rename to docs/services/azure-monitor/azure-sre-agent/images/s1-email-preview.png diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/s1-three-panel.png b/docs/services/azure-monitor/azure-sre-agent/images/s1-three-panel.png similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/s1-three-panel.png rename to docs/services/azure-monitor/azure-sre-agent/images/s1-three-panel.png diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/images/sre-agent-process.png b/docs/services/azure-monitor/azure-sre-agent/images/sre-agent-process.png similarity index 100% rename from docs/guides/azure-monitor/azure-sre-agent-overview/images/sre-agent-process.png rename to docs/services/azure-monitor/azure-sre-agent/images/sre-agent-process.png diff --git a/docs/guides/azure-monitor/sre-agent-incident-runbook/index.md b/docs/services/azure-monitor/azure-sre-agent/incident-runbook/index.md similarity index 98% rename from docs/guides/azure-monitor/sre-agent-incident-runbook/index.md rename to docs/services/azure-monitor/azure-sre-agent/incident-runbook/index.md index 02973aa..054ba82 100644 --- a/docs/guides/azure-monitor/sre-agent-incident-runbook/index.md +++ b/docs/services/azure-monitor/azure-sre-agent/incident-runbook/index.md @@ -20,6 +20,9 @@ tags: - troubleshooting - monitoring - reliability +topic_order: 8 +redirect_from: +- guides/azure-monitor/sre-agent-incident-runbook/index.md --- # Azure SRE Agent Event Lab Incident Runbook diff --git a/docs/guides/azure-monitor/azure-sre-agent-overview/index.md b/docs/services/azure-monitor/azure-sre-agent/index.md similarity index 97% rename from docs/guides/azure-monitor/azure-sre-agent-overview/index.md rename to docs/services/azure-monitor/azure-sre-agent/index.md index 3dba48f..f09671d 100644 --- a/docs/guides/azure-monitor/azure-sre-agent-overview/index.md +++ b/docs/services/azure-monitor/azure-sre-agent/index.md @@ -20,6 +20,8 @@ tags: - ai-agents - monitoring - troubleshooting +redirect_from: +- guides/azure-monitor/azure-sre-agent-overview/index.md --- # Azure SRE Agent 소개 @@ -262,7 +264,7 @@ Azure SRE Agent는 다음 기능을 제품에서 기본으로 지원합니다. ![Azure SRE Agent의 인시던트 대응 흐름](images/sre-agent-process.png) -[편집 가능한 SVG 보기](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/sre-agent-event-lab/assets/briefing/sre-agent-process.svg) +[편집 가능한 SVG 보기](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/sre-agent-process.svg) 이번 실증에서는 대응 계획을 공개 API로 자동 구성하는 데 제약이 있어 Azure SRE Agent의 HTTP Trigger를 사용했습니다. @@ -301,7 +303,7 @@ Azure Monitor 경고 ![주문 API HTTP 500 실증 요약](images/s1-three-panel.png) -[편집 가능한 SVG 보기](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/sre-agent-event-lab/assets/briefing/s1-three-panel.svg) +[편집 가능한 SVG 보기](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/s1-three-panel.svg) ### 상황 @@ -336,7 +338,7 @@ Azure SRE Agent가 정리한 근본 원인과 조치 방안을 작업 항목으 ![실제 GitHub Issue #43](images/github-issue.png) - [GitHub Issue #43 열기](https://github.com/hellices/devguidesample/issues/43) -- [Issue 본문 보기](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/sre-agent-event-lab/assets/notifications/s1-github-issue.md) +- [Issue 본문 보기](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-github-issue.md) Issue에는 다음 내용을 포함했습니다. @@ -356,8 +358,8 @@ Issue에는 다음 내용을 포함했습니다. ![Outlook 메일 초안](images/s1-email-preview.png) -- [HTML 메일 보기](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/sre-agent-event-lab/assets/notifications/s1-incident-summary.html) -- [RFC 5322 메일 파일 보기](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/sre-agent-event-lab/assets/notifications/s1-incident-summary.eml) +- [HTML 메일 보기](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-incident-summary.html) +- [RFC 5322 메일 파일 보기](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-incident-summary.eml) 실제 운영에서는 Outlook 커넥터의 메일 보내기 작업을 사용할 수 있습니다. @@ -376,7 +378,7 @@ Issue에는 다음 내용을 포함했습니다. | 주문 API 지연 | 성공한 요청의 응답 시간, 외부 종속성, 지연 설정 | `ORDER_DELAY_MS=4000`을 확인했습니다. | | Blob 권한 오류 | 역할 삭제 이력, Blob 403, API 503 | 역할 삭제를 근본 원인으로 확인했습니다. 복구 확인 대상은 한 차례 잘못 선택해 한계로 기록했습니다. | -상세 수치, 시간 순서, 평가 기준은 [실제 동작 검증 결과](../../../research/azure-monitor/sre-agent-validation-results/index.md)에서 확인할 수 있습니다. +상세 수치, 시간 순서, 평가 기준은 [실제 동작 검증 결과](validation-results/index.md)에서 확인할 수 있습니다. ## Dynamic Thresholds와 함께 사용할 수 있나요? @@ -389,7 +391,7 @@ Issue에는 다음 내용을 포함했습니다. 처음에는 기존 고정 임계값을 유지하고, Dynamic Thresholds를 별도의 관찰용 경고로 추가하는 방식을 권장합니다. 충분한 학습 기간을 거친 뒤 오탐과 누락을 비교해 실제 대응 흐름에 연결합니다. -자세한 내용은 [Dynamic Thresholds 연계 가이드](../sre-agent-dynamic-thresholds/index.md)를 참고하세요. +자세한 내용은 [Dynamic Thresholds 연계 가이드](dynamic-thresholds/index.md)를 참고하세요. ## 도입 전에 확인해야 할 사전 조건 @@ -671,5 +673,5 @@ Agent Hooks를 사용하면 에이전트가 결과를 반환하기 직전이나 ### 실증 자료 -- [실제 동작 검증 결과](../../../research/azure-monitor/sre-agent-validation-results/index.md) -- [실험 환경과 재현 방법](../../../labs/azure-monitor/sre-agent-event-lab/index.md) +- [실제 동작 검증 결과](validation-results/index.md) +- [실험 환경과 재현 방법](event-lab/index.md) diff --git a/docs/labs/azure-monitor/sre-agent-results/index.md b/docs/services/azure-monitor/azure-sre-agent/results/index.md similarity index 95% rename from docs/labs/azure-monitor/sre-agent-results/index.md rename to docs/services/azure-monitor/azure-sre-agent/results/index.md index f34b272..79eb1a0 100644 --- a/docs/labs/azure-monitor/sre-agent-results/index.md +++ b/docs/services/azure-monitor/azure-sre-agent/results/index.md @@ -21,6 +21,9 @@ tags: - ai-agents - monitoring - diagnostics +topic_order: 6 +redirect_from: +- labs/azure-monitor/sre-agent-results/index.md --- # 05. 결과 읽기와 채점 @@ -36,7 +39,7 @@ tags: ## 실행 명령 ```bash -cd samples/azure-monitor/source-material/sre-agent-event-lab +cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab app/.venv/bin/python scripts/score.py --evidence-root evidence ``` @@ -118,4 +121,4 @@ python3 scripts/generate_notifications.py \ azd down --purge ``` -정리 훅이 무엇을 지우는지, 확인 프롬프트에서 취소하면 무엇이 남는지는 [README의 정리 절](../sre-agent-event-lab/index.md)에 있습니다. +정리 훅이 무엇을 지우는지, 확인 프롬프트에서 취소하면 무엇이 남는지는 [README의 정리 절](../event-lab/index.md)에 있습니다. diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/.env.example b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/.env.example similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/.env.example rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/.env.example diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/.gitignore b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/.gitignore similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/.gitignore rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/.gitignore diff --git a/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/README.md b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/README.md new file mode 100644 index 0000000..25549e6 --- /dev/null +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/README.md @@ -0,0 +1,14 @@ +# Azure SRE Agent event lab + +This runnable sample contains the application, infrastructure, scenario scripts, +captured evidence, and notification assets used by the Azure SRE Agent topic. + +From the repository root: + +```bash +cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab +azd up +``` + +Follow the [event lab instructions](../../event-lab/index.md) for setup, +scenario execution, validation, and cleanup. diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/app/.dockerignore b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/.dockerignore similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/app/.dockerignore rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/.dockerignore diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/app/Dockerfile b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/Dockerfile similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/app/Dockerfile rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/Dockerfile diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/app/main.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/main.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/app/main.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/main.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/app/requirements-dev.txt b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/requirements-dev.txt similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/app/requirements-dev.txt rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/requirements-dev.txt diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/app/requirements.txt b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/requirements.txt similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/app/requirements.txt rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/requirements.txt diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/app/telemetry.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/telemetry.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/app/telemetry.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/telemetry.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/app/tests/conftest.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/tests/conftest.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/app/tests/conftest.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/tests/conftest.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/app/tests/test_main.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/tests/test_main.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/app/tests/test_main.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/tests/test_main.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/app/tests/test_telemetry.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/tests/test_telemetry.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/app/tests/test_telemetry.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/app/tests/test_telemetry.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/briefing/s1-agent-conclusion.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/s1-agent-conclusion.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/briefing/s1-agent-conclusion.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/s1-agent-conclusion.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/briefing/s1-three-panel.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/s1-three-panel.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/briefing/s1-three-panel.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/s1-three-panel.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/briefing/s1-three-panel.svg b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/s1-three-panel.svg similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/briefing/s1-three-panel.svg rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/s1-three-panel.svg diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/briefing/sre-agent-process.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/sre-agent-process.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/briefing/sre-agent-process.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/sre-agent-process.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/briefing/sre-agent-process.svg b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/sre-agent-process.svg similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/briefing/sre-agent-process.svg rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/sre-agent-process.svg diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1-before-bridge/01-alert-fired.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1-before-bridge/01-alert-fired.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1-before-bridge/01-alert-fired.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1-before-bridge/01-alert-fired.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1-before-bridge/02-thread-not-created.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1-before-bridge/02-thread-not-created.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1-before-bridge/02-thread-not-created.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1-before-bridge/02-thread-not-created.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1-before-bridge/03-investigation-missing.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1-before-bridge/03-investigation-missing.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1-before-bridge/03-investigation-missing.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1-before-bridge/03-investigation-missing.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1-before-bridge/04-conclusion-missing.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1-before-bridge/04-conclusion-missing.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1-before-bridge/04-conclusion-missing.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1-before-bridge/04-conclusion-missing.png diff --git a/docs/research/azure-monitor/sre-agent-validation-results/images/investigation.gif b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1-before-bridge/investigation.gif similarity index 100% rename from docs/research/azure-monitor/sre-agent-validation-results/images/investigation.gif rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1-before-bridge/investigation.gif diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1-before-bridge/timeline.md b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1-before-bridge/timeline.md similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1-before-bridge/timeline.md rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1-before-bridge/timeline.md diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1-before-bridge/timeline.mmd b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1-before-bridge/timeline.mmd similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1-before-bridge/timeline.mmd rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1-before-bridge/timeline.mmd diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/01-alert-fired.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/01-alert-fired.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/01-alert-fired.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/01-alert-fired.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/02-thread-created.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/02-thread-created.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/02-thread-created.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/02-thread-created.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/03-investigating.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/03-investigating.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/03-investigating.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/03-investigating.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/04-investigating.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/04-investigating.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/04-investigating.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/04-investigating.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/05-investigating.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/05-investigating.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/05-investigating.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/05-investigating.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/06-investigating.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/06-investigating.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/06-investigating.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/06-investigating.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/07-conclusion.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/07-conclusion.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/07-conclusion.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/07-conclusion.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/investigation.gif b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/investigation.gif similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/investigation.gif rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/investigation.gif diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/timeline.md b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/timeline.md similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/timeline.md rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/timeline.md diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/timeline.mmd b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/timeline.mmd similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/timeline.mmd rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/timeline.mmd diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/01-alert-fired.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/01-alert-fired.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/01-alert-fired.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/01-alert-fired.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/02-thread-created.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/02-thread-created.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/02-thread-created.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/02-thread-created.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/03-investigating.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/03-investigating.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/03-investigating.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/03-investigating.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/04-investigating.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/04-investigating.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/04-investigating.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/04-investigating.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/05-investigating.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/05-investigating.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/05-investigating.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/05-investigating.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/06-investigating.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/06-investigating.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/06-investigating.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/06-investigating.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/07-conclusion.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/07-conclusion.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/07-conclusion.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/07-conclusion.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/investigation.gif b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/investigation.gif similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/investigation.gif rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/investigation.gif diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/timeline.md b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/timeline.md similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/timeline.md rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/timeline.md diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/timeline.mmd b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/timeline.mmd similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/timeline.mmd rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/timeline.mmd diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/01-alert-fired.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/01-alert-fired.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/01-alert-fired.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/01-alert-fired.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/02-thread-created.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/02-thread-created.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/02-thread-created.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/02-thread-created.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/03-investigating.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/03-investigating.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/03-investigating.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/03-investigating.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/04-investigating.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/04-investigating.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/04-investigating.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/04-investigating.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/05-investigating.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/05-investigating.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/05-investigating.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/05-investigating.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/06-investigating.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/06-investigating.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/06-investigating.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/06-investigating.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/07-conclusion.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/07-conclusion.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/07-conclusion.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/07-conclusion.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/investigation.gif b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/investigation.gif similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/investigation.gif rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/investigation.gif diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/timeline.md b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.md similarity index 98% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/timeline.md rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.md index 9fbc61e..0e2e07b 100644 --- a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/timeline.md +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.md @@ -28,4 +28,4 @@ | 2026-08-12T08:46:03.604102Z | 232s | investigating | sre-agent | **Evaluating KQL issues** I'm working through a problem with KQL where I'm trying to use `union kind=outer dependencies, requests`, but I suspect there are issues with different columns. The app-insights CLI might be referencing outdated table names, leading to invalid property errors. I think I can query requests between 8:42:45 and 8:45:30 and aggregate them separately from dependencies. Currently, there’s a 503 error and the container health is false, even though one replica is ready. I’ll consider mitigation options without a restart and check logs for errors. | | 2026-08-12T08:46:04.820660Z | 233s | investigating | sre-agent | **Analyzing query for failures** I need to run two queries to check for failures after adding a role. The current time is 08:45, and I want to specifically query the timestamp from the `dependencies` table. The KQL I'm thinking of is `dependencies \| where timestamp... \| summarize total=count(), failures=countif(resultCode == '403'), lastFailure=maxif(timestamp, resultCode == "403")`. I'm wondering if `maxif` is supported, and I might also simplify it to just check for `resultCode=="403"` since 8:42:45. I need to run a separate query for the requests with a 503 error and then check the role assignment state to ensure it’s processed correctly. | | 2026-08-12T08:46:06.560018Z | 235s | investigating | sre-agent | The role assignment is present, but recovery has not completed: at `08:45 UTC`, the public `/api/documents` endpoint still returns `503` while the replica remains ready. I’m separating post-restore dependency and request telemetry to distinguish RBAC propagation from a second fault. | -| 2026-08-12T08:46:37.172586Z | 266s | conclusion | sre-agent | ## Incident summary - **Alert:** `[SRE-LAB-S3] Blob dependency failures exceeded` (Sev2), fired **2026-08-12 08:42:11 UTC**. - **Affected path:** `GET /api/documents` on `ca-sre-event-lab`; Blob target `stexample00000000/documents`. - **Onset:** first `503` at **08:40:08.363 UTC**; first Blob `403` at **08:40:08.375 UTC**. ## Root cause The workload managed identity `` lost its container-scoped **Storage Blob Data Reader** assignment at **08:40:07.988 UTC**. The storage call then failed with `AuthorizationPermissionMismatch`, which the app surfaced as `503`. ## Evidence - Activity Log shows `roleAssignments/delete` for the `documents` container at **08:40:07.988 UTC**. - A representative trace shows `list_blobs` failing against Blob Storage with `403` and `AuthorizationPermissionMismatch`. - The alert window contained **60** Blob `403` failures. - The role was restored at **08:42:48.660 UTC**; current assignment is `Storage Blob Data Reader` at the original container scope. ## Current status Recovery is **still propagating**: a Blob `403` occurred at **08:45:25 UTC**, and the external endpoint returned `503` at approximately **08:45 UTC**. The Container App revision is otherwise running with one ready replica. ## Smallest safe mitigation No additional change is recommended: the least-privilege role has already been restored at the correct scope. Allow RBAC propagation, then recheck `/api/documents` and Blob `403` telemetry. Do not restart or redeploy solely for this issue. The connected repository contains managed-identity Blob-client guidance but not this deployed lab workload’s source: [bench_runner.py](https://github.com/hellices/devguidesample/blob/main/samples/azure-ai-search/source-material/custom_vectorization/bench/bench_runner.py#L49). | +| 2026-08-12T08:46:37.172586Z | 266s | conclusion | sre-agent | ## Incident summary - **Alert:** `[SRE-LAB-S3] Blob dependency failures exceeded` (Sev2), fired **2026-08-12 08:42:11 UTC**. - **Affected path:** `GET /api/documents` on `ca-sre-event-lab`; Blob target `stexample00000000/documents`. - **Onset:** first `503` at **08:40:08.363 UTC**; first Blob `403` at **08:40:08.375 UTC**. ## Root cause The workload managed identity `` lost its container-scoped **Storage Blob Data Reader** assignment at **08:40:07.988 UTC**. The storage call then failed with `AuthorizationPermissionMismatch`, which the app surfaced as `503`. ## Evidence - Activity Log shows `roleAssignments/delete` for the `documents` container at **08:40:07.988 UTC**. - A representative trace shows `list_blobs` failing against Blob Storage with `403` and `AuthorizationPermissionMismatch`. - The alert window contained **60** Blob `403` failures. - The role was restored at **08:42:48.660 UTC**; current assignment is `Storage Blob Data Reader` at the original container scope. ## Current status Recovery is **still propagating**: a Blob `403` occurred at **08:45:25 UTC**, and the external endpoint returned `503` at approximately **08:45 UTC**. The Container App revision is otherwise running with one ready replica. ## Smallest safe mitigation No additional change is recommended: the least-privilege role has already been restored at the correct scope. Allow RBAC propagation, then recheck `/api/documents` and Blob `403` telemetry. Do not restart or redeploy solely for this issue. The connected repository contains managed-identity Blob-client guidance but not this deployed lab workload’s source: [bench_runner.py](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/bench_runner.py#L49). | diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/timeline.mmd b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.mmd similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/timeline.mmd rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.mmd diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/notifications/github-issue.json b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/github-issue.json similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/notifications/github-issue.json rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/github-issue.json diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/notifications/github-issue.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/github-issue.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/notifications/github-issue.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/github-issue.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/notifications/s1-email-preview.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-email-preview.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/notifications/s1-email-preview.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-email-preview.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/notifications/s1-github-issue.md b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-github-issue.md similarity index 92% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/notifications/s1-github-issue.md rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-github-issue.md index cd68f0f..41177c5 100644 --- a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/notifications/s1-github-issue.md +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-github-issue.md @@ -43,5 +43,5 @@ Resolved. A succeeding revision uses `FAILURE_MODE=none`, receives traffic, and ## Tracking - Agent thread ID: `` -- Detailed validation appendix: [Azure SRE Agent validation results](https://github.com/hellices/devguidesample/blob/main/docs/research/azure-monitor/sre-agent-validation-results/index.md) +- Detailed validation appendix: [Azure SRE Agent validation results](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/azure-sre-agent/validation-results/index.md) - Generated from actual Azure SRE Agent evidence; no resource change was executed by the Agent. diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/notifications/s1-incident-summary.eml b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-incident-summary.eml similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/notifications/s1-incident-summary.eml rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-incident-summary.eml diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/notifications/s1-incident-summary.html b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-incident-summary.html similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/notifications/s1-incident-summary.html rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-incident-summary.html diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/agent-reasoning-flow.svg b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/agent-reasoning-flow.svg similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/agent-reasoning-flow.svg rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/agent-reasoning-flow.svg diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/azure-sre-agent-networking-vnet.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/azure-sre-agent-networking-vnet.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/azure-sre-agent-networking-vnet.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/azure-sre-agent-networking-vnet.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/custom-skill-flow.svg b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/custom-skill-flow.svg similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/custom-skill-flow.svg rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/custom-skill-flow.svg diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/diagnose-azure-services.svg b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/diagnose-azure-services.svg similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/diagnose-azure-services.svg rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/diagnose-azure-services.svg diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/incident-platform-flow.svg b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/incident-platform-flow.svg similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/incident-platform-flow.svg rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/incident-platform-flow.svg diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/incident-response-flow.svg b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/incident-response-flow.svg similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/incident-response-flow.svg rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/incident-response-flow.svg diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/knowledge-sources.svg b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/knowledge-sources.svg similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/knowledge-sources.svg rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/knowledge-sources.svg diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/managed-connectors-icon-grid.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/managed-connectors-icon-grid.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/managed-connectors-icon-grid.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/managed-connectors-icon-grid.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/memory-auto-learning.svg b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/memory-auto-learning.svg similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/memory-auto-learning.svg rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/memory-auto-learning.svg diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/memory-unified-search.svg b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/memory-unified-search.svg similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/memory-unified-search.svg rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/memory-unified-search.svg diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/notification-paths.svg b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/notification-paths.svg similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/notification-paths.svg rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/notification-paths.svg diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/operations-hub-overview-tab.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/operations-hub-overview-tab.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/operations-hub-overview-tab.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/operations-hub-overview-tab.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/permission-flow.svg b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/permission-flow.svg similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/permission-flow.svg rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/permission-flow.svg diff --git a/docs/labs/azure-monitor/sre-agent-event-lab-setup/images/portal-complete-setup-page.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/portal-complete-setup-page.png similarity index 100% rename from docs/labs/azure-monitor/sre-agent-event-lab-setup/images/portal-complete-setup-page.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/portal-complete-setup-page.png diff --git a/docs/labs/azure-monitor/sre-agent-event-lab-setup/images/portal-incident-response-plans-list.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/portal-incident-response-plans-list.png similarity index 100% rename from docs/labs/azure-monitor/sre-agent-event-lab-setup/images/portal-incident-response-plans-list.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/portal-incident-response-plans-list.png diff --git a/docs/labs/azure-monitor/sre-agent-event-lab-setup/images/portal-response-plan-autonomy-step.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/portal-response-plan-autonomy-step.png similarity index 100% rename from docs/labs/azure-monitor/sre-agent-event-lab-setup/images/portal-response-plan-autonomy-step.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/portal-response-plan-autonomy-step.png diff --git a/docs/labs/azure-monitor/sre-agent-event-lab-setup/images/portal-setup-status-bar.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/portal-setup-status-bar.png similarity index 100% rename from docs/labs/azure-monitor/sre-agent-event-lab-setup/images/portal-setup-status-bar.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/portal-setup-status-bar.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/portal-sub-agent-canvas-full.png b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/portal-sub-agent-canvas-full.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/portal-sub-agent-canvas-full.png rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/portal-sub-agent-canvas-full.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/root-cause-analysis.svg b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/root-cause-analysis.svg similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/root-cause-analysis.svg rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/root-cause-analysis.svg diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/run-modes-comparison.svg b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/run-modes-comparison.svg similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/run-modes-comparison.svg rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/official/run-modes-comparison.svg diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/azure.yaml b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/azure.yaml similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/azure.yaml rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/azure.yaml diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/infra/alerts.bicep b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/alerts.bicep similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/infra/alerts.bicep rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/alerts.bicep diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/infra/lab.bicep b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/lab.bicep similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/infra/lab.bicep rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/lab.bicep diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/infra/main.bicep b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/main.bicep similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/infra/main.bicep rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/main.bicep diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/infra/main.parameters.json b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/main.parameters.json similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/infra/main.parameters.json rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/main.parameters.json diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/infra/observability.bicep b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/observability.bicep similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/infra/observability.bicep rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/observability.bicep diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/infra/tests/test_alerts_bicep.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/tests/test_alerts_bicep.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/infra/tests/test_alerts_bicep.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/tests/test_alerts_bicep.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/infra/tests/test_azd_project.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/tests/test_azd_project.py similarity index 99% rename from samples/azure-monitor/source-material/sre-agent-event-lab/infra/tests/test_azd_project.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/tests/test_azd_project.py index f6d8755..a8804b2 100644 --- a/samples/azure-monitor/source-material/sre-agent-event-lab/infra/tests/test_azd_project.py +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/tests/test_azd_project.py @@ -6,13 +6,14 @@ LAB_ROOT = Path(__file__).parents[2] -REPO_ROOT = Path(__file__).parents[6] +REPO_ROOT = Path(__file__).parents[8] LAB_PAGE = ( REPO_ROOT / "docs" - / "labs" + / "services" / "azure-monitor" - / "sre-agent-event-lab" + / "azure-sre-agent" + / "event-lab" / "index.md" ) PLACEHOLDER_IMAGE = "mcr.microsoft.com/azuredocs/containerapps-helloworld:latest" diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/infra/tests/test_workload_networking.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/tests/test_workload_networking.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/infra/tests/test_workload_networking.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/tests/test_workload_networking.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/infra/workload.bicep b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/workload.bicep similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/infra/workload.bicep rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/infra/workload.bicep diff --git a/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/sample.yml b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/sample.yml new file mode 100644 index 0000000..5c2b8a9 --- /dev/null +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/sample.yml @@ -0,0 +1,14 @@ +title: Azure SRE Agent event lab +description: Deployable lab and evidence assets for the Azure SRE Agent scenarios. +kind: runnable +used_by: +- index +- event-lab +- setup +- scenario-http-500 +- scenario-latency +- scenario-blob-permission +- results +- validation-results +- incident-runbook +- dynamic-thresholds diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/azd-configure.sh b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/azd-configure.sh similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/azd-configure.sh rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/azd-configure.sh diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/azd-deploy-app.sh b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/azd-deploy-app.sh similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/azd-deploy-app.sh rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/azd-deploy-app.sh diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/azd-postprovision-local.sh b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/azd-postprovision-local.sh similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/azd-postprovision-local.sh rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/azd-postprovision-local.sh diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/capture_agent.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/capture_agent.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/capture_agent.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/capture_agent.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/capture_model.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/capture_model.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/capture_model.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/capture_model.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/cleanup-external.sh b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/cleanup-external.sh similarity index 97% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/cleanup-external.sh rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/cleanup-external.sh index 2610c4d..3792f28 100755 --- a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/cleanup-external.sh +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/cleanup-external.sh @@ -160,7 +160,7 @@ if ! RECORDED_ASSIGNMENTS="$(jq -r ' | join("|") ' "${CLEANUP_SETUP_FILE}" 2>/dev/null)"; then echo "Agent setup evidence is not valid JSON: ${CLEANUP_SETUP_FILE}" >&2 - echo "Rewrite evidence/agent-setup.json as shown in docs/labs/azure-monitor/sre-agent-event-lab-setup/index.md, then run: python3 scripts/lab_state.py acknowledge-agent" >&2 + echo "Rewrite evidence/agent-setup.json as shown in docs/services/azure-monitor/azure-sre-agent/setup/index.md, then run: python3 scripts/lab_state.py acknowledge-agent" >&2 exit 1 fi readonly RECORDED_ASSIGNMENTS @@ -260,12 +260,12 @@ while IFS='|' read -r assignment_key assignment_id principal_key expected_princi echo "${assignment_key} is empty for principal ${expected_principal_id:-unknown}." >&2 echo "Find the live ID: az role assignment list --assignee-object-id ${expected_principal_id:-} --role \"Monitoring Contributor\" --scope ${SUBSCRIPTION_SCOPE} --query \"[0].id\" -o tsv" >&2 echo "If no assignment is returned and teardown should continue, clear both ${assignment_key} and ${principal_key} in the evidence file." >&2 - echo "Update evidence/agent-setup.json as shown in docs/labs/azure-monitor/sre-agent-event-lab-setup/index.md, then run: python3 scripts/lab_state.py acknowledge-agent" >&2 + echo "Update evidence/agent-setup.json as shown in docs/services/azure-monitor/azure-sre-agent/setup/index.md, then run: python3 scripts/lab_state.py acknowledge-agent" >&2 exit 1 fi if [[ -z "${expected_principal_id}" ]]; then echo "Incomplete Agent setup evidence: ${assignment_id} was recorded without ${principal_key}." >&2 - echo "Update evidence/agent-setup.json as shown in docs/labs/azure-monitor/sre-agent-event-lab-setup/index.md, then run: python3 scripts/lab_state.py acknowledge-agent" >&2 + echo "Update evidence/agent-setup.json as shown in docs/services/azure-monitor/azure-sre-agent/setup/index.md, then run: python3 scripts/lab_state.py acknowledge-agent" >&2 exit 1 fi case "${VERIFIED_ASSIGNMENT_IDS}" in diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/common.sh b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/common.sh similarity index 99% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/common.sh rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/common.sh index c1d189f..2414726 100755 --- a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/common.sh +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/common.sh @@ -274,7 +274,7 @@ evidence_dir_path() { # create_evidence_dir SCENARIO -- name it and create it in one step, for # callers that write into it immediately and have nothing left to refuse # them (the baseline step in -# `docs/labs/azure-monitor/sre-agent-event-lab-setup/index.md`). +# `docs/services/azure-monitor/azure-sre-agent/setup/index.md`). create_evidence_dir() { local directory directory="$(evidence_dir_path "$1")" diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/generate_notifications.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/generate_notifications.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/generate_notifications.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/generate_notifications.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/lab-env.sh b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/lab-env.sh similarity index 99% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/lab-env.sh rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/lab-env.sh index 958322f..1667eb0 100755 --- a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/lab-env.sh +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/lab-env.sh @@ -2,7 +2,7 @@ # Resolves the lab's configuration once and exports it into the current # shell. Source it; do not execute it: # -# cd samples/azure-monitor/source-material/sre-agent-event-lab +# cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab # source ./scripts/lab-env.sh # # Every value here is a name, an ID or a URL that `azd provision` already diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/lab_state.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/lab_state.py similarity index 98% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/lab_state.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/lab_state.py index 6afeb9e..24fdd2c 100644 --- a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/lab_state.py +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/lab_state.py @@ -178,9 +178,9 @@ def terminal_state(events: Iterable[Dict[str, Any]]) -> str: # messages name it instead of a command, because the lab is run by hand: # there is no script that performs a scenario. SCENARIO_GUIDES = { - "s1": "docs/labs/azure-monitor/sre-agent-scenario-http-500/index.md", - "s2": "docs/labs/azure-monitor/sre-agent-scenario-latency/index.md", - "s3": "docs/labs/azure-monitor/sre-agent-scenario-blob-permission/index.md", + "s1": "docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md", + "s2": "docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md", + "s3": "docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md", } @@ -188,7 +188,7 @@ def scenario_guide(scenario: str) -> str: # A remedy that cannot be acted on is not a remedy: an unmapped # scenario still has to say where to look. return SCENARIO_GUIDES.get( - scenario, "the matching page under docs/labs/azure-monitor/" + scenario, "the matching page under docs/services/azure-monitor/azure-sre-agent/" ) @@ -491,7 +491,7 @@ def _remedy(self, missing: Sequence[str]) -> str: remedies = { "baseline_passed": ( "Run the baseline steps in " - "docs/labs/azure-monitor/sre-agent-event-lab-setup/index.md, then: " + "docs/services/azure-monitor/azure-sre-agent/setup/index.md, then: " "lab_state.py mark baseline_passed" ), "agent_setup_acknowledged": "Run: lab_state.py acknowledge-agent", diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/loadgen.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/loadgen.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/loadgen.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/loadgen.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/query-evidence.sh b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/query-evidence.sh similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/query-evidence.sh rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/query-evidence.sh diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/render_briefing_assets.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/render_briefing_assets.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/render_briefing_assets.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/render_briefing_assets.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/render_capture.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/render_capture.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/render_capture.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/render_capture.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/score.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/score.py similarity index 99% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/score.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/score.py index c7d3161..203bbba 100644 --- a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/score.py +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/score.py @@ -333,7 +333,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int: if not any(state.capture_status(scenario) for scenario in SCENARIOS): print( "No captured scenario evidence in {0}. Run the s1 steps in " - "docs/labs/azure-monitor/sre-agent-scenario-http-500/index.md, " + "docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md, " "capture included.".format(evidence_root), file=sys.stderr, ) diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/setup-venv.sh b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/setup-venv.sh similarity index 98% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/setup-venv.sh rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/setup-venv.sh index 4536ed8..8f586f2 100755 --- a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/setup-venv.sh +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/setup-venv.sh @@ -6,7 +6,7 @@ set -euo pipefail # by `-r requirements.txt` at the top of `requirements-dev.txt`), Pillow for # `render_capture.py`'s PNG/GIF rendering, and pytest/httpx for `app/tests`. # The capture step in each scenario guide and -# `docs/labs/azure-monitor/sre-agent-results/index.md`'s +# `docs/services/azure-monitor/azure-sre-agent/results/index.md`'s # notification step both # run under this interpreter. # diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/azd_common_harness.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/azd_common_harness.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/azd_common_harness.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/azd_common_harness.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/azd_fake.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/azd_fake.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/azd_fake.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/azd_fake.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/cleanup_harness.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/cleanup_harness.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/cleanup_harness.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/cleanup_harness.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/deploy_app_harness.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/deploy_app_harness.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/deploy_app_harness.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/deploy_app_harness.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/lab_script_harness.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/lab_script_harness.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/lab_script_harness.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/lab_script_harness.py diff --git a/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/published_layout.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/published_layout.py new file mode 100644 index 0000000..3079352 --- /dev/null +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/published_layout.py @@ -0,0 +1,41 @@ +"""Canonical repository paths for the SRE lab's published documentation.""" + +from __future__ import annotations + +from pathlib import Path + + +REPO_ROOT = Path(__file__).parents[8] +LAB_ROOT = Path(__file__).parents[2] +DOCS_ROOT = REPO_ROOT / "docs" +TOPIC_ROOT = DOCS_ROOT / "services" / "azure-monitor" / "azure-sre-agent" + +README = TOPIC_ROOT / "event-lab" / "index.md" +GUIDE_PATHS = { + "01-agent-setup.md": TOPIC_ROOT / "setup" / "index.md", + "02-scenario-s1.md": TOPIC_ROOT / "scenario-http-500" / "index.md", + "03-scenario-s2.md": TOPIC_ROOT / "scenario-latency" / "index.md", + "04-scenario-s3.md": TOPIC_ROOT / "scenario-blob-permission" / "index.md", + "05-results.md": TOPIC_ROOT / "results" / "index.md", +} + + +class PublishedGuideDirectory: + """Provide the former directory interface over published page bundles.""" + + def __truediv__(self, name: str | Path) -> Path: + return GUIDE_PATHS[str(name)] + + def glob(self, pattern: str): + if pattern != "*.md": + return iter(()) + return iter(GUIDE_PATHS.values()) + + +GUIDES = PublishedGuideDirectory() +RESULTS_GUIDE = GUIDE_PATHS["05-results.md"] +RUNBOOK = TOPIC_ROOT / "incident-runbook" / "index.md" +DYNAMIC_THRESHOLDS = TOPIC_ROOT / "dynamic-thresholds" / "index.md" +VALIDATION_RESULTS = TOPIC_ROOT / "validation-results" / "index.md" +BRIEFING = TOPIC_ROOT / "index.md" +OFFICIAL_ASSETS = LAB_ROOT / "assets" / "official" diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_azd_deploy_app.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_azd_deploy_app.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_azd_deploy_app.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_azd_deploy_app.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_azd_env.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_azd_env.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_azd_env.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_azd_env.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_azd_hooks.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_azd_hooks.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_azd_hooks.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_azd_hooks.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_briefing_assets.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_briefing_assets.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_briefing_assets.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_briefing_assets.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_briefing_docs.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_briefing_docs.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_briefing_docs.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_briefing_docs.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_capture_agent.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_capture_agent.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_capture_agent.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_capture_agent.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_capture_model.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_capture_model.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_capture_model.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_capture_model.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_cleanup_external.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_cleanup_external.py similarity index 99% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_cleanup_external.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_cleanup_external.py index f2c161e..eda3c01 100644 --- a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_cleanup_external.py +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_cleanup_external.py @@ -419,7 +419,7 @@ def test_cleanup_refuses_malformed_evidence(tmp_path): run = run_cleanup(tmp_path, ["--yes"], raw_evidence="{not json at all") assert run.returncode != 0 - assert "docs/labs/azure-monitor/sre-agent-event-lab-setup/index.md" in run.stderr + assert "docs/services/azure-monitor/azure-sre-agent/setup/index.md" in run.stderr assert "agent-setup.json" in run.stderr assert "role assignment delete" not in run.az_calls diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_common.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_common.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_common.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_common.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_dynamic_threshold_brief.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_dynamic_threshold_brief.py similarity index 93% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_dynamic_threshold_brief.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_dynamic_threshold_brief.py index 05d1825..8e4ee32 100644 --- a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_dynamic_threshold_brief.py +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_dynamic_threshold_brief.py @@ -5,8 +5,8 @@ from PIL import Image -REPO_ROOT = Path(__file__).parents[6] -BRIEF = REPO_ROOT / "docs" / "research" / "azure-monitor" / "dynamic-thresholds-brief" / "index.md" +REPO_ROOT = Path(__file__).parents[8] +BRIEF = REPO_ROOT / "docs" / "services" / "azure-monitor" / "dynamic-thresholds-brief" / "index.md" ASSET = BRIEF.parent / "images" / "dynamic-threshold-preview-chart.png" ARTICLE = "https://learn.microsoft.com/en-us/azure/azure-monitor/alerts/alerts-dynamic-thresholds" RAW_MEDIA = ( @@ -15,7 +15,7 @@ ) OFFICIAL_ASSETS = {"dynamic-threshold-preview-chart.png"} # To update this digest, download RAW_MEDIA and run: -# shasum -a 256 docs/research/azure-monitor/dynamic-thresholds-brief/images/dynamic-threshold-preview-chart.png +# shasum -a 256 docs/services/azure-monitor/dynamic-thresholds-brief/images/dynamic-threshold-preview-chart.png EXPECTED_ASSET_SHA256 = "4688901b73dff95c47d6d87c6d73f774dcb613fec38b757d1a76953df098636c" diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_lab_env.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_lab_env.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_lab_env.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_lab_env.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_lab_state.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_lab_state.py similarity index 98% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_lab_state.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_lab_state.py index 3b2eb1d..799d5d2 100644 --- a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_lab_state.py +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_lab_state.py @@ -51,9 +51,9 @@ def test_every_scenario_has_a_guide_to_send_an_operator_to(): import lab_state expected = { - "s1": "docs/labs/azure-monitor/sre-agent-scenario-http-500/index.md", - "s2": "docs/labs/azure-monitor/sre-agent-scenario-latency/index.md", - "s3": "docs/labs/azure-monitor/sre-agent-scenario-blob-permission/index.md", + "s1": "docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md", + "s2": "docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md", + "s3": "docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md", } assert lab_state.SCENARIO_GUIDES == expected assert set(lab_state.SCENARIO_GUIDES) == set(lab_state.SCENARIOS) @@ -582,7 +582,7 @@ def test_the_refusal_lists_every_blocker_earliest_first(tmp_path): assert message.index("s2") < message.index("s3"), message assert "running" in message and "failed" in message assert "mark-failed s2" in message - assert "docs/labs/azure-monitor/sre-agent-scenario-blob-permission/index.md" in message + assert "docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md" in message def test_a_repair_is_refused_while_an_earlier_run_is_still_running(tmp_path): @@ -614,7 +614,7 @@ def test_the_refusal_names_the_blocking_scenario_its_status_and_a_remedy(tmp_pat message = str(refusal.value) assert "s2" in message assert "failed" in message - assert "docs/labs/azure-monitor/sre-agent-scenario-latency/index.md" in message, message + assert "docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md" in message, message def test_the_refusal_for_a_running_scenario_names_how_to_end_it(tmp_path): @@ -646,7 +646,7 @@ def test_the_ordered_remedy_never_tells_an_operator_to_restart_a_running_run(tmp message = str(refusal.value) assert "s1_recovered" in message - assert "docs/labs/azure-monitor/sre-agent-scenario-http-500/index.md" not in message, message + assert "docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md" not in message, message assert "mark-failed s1" in message, message @@ -790,16 +790,16 @@ def test_a_conclusion_cannot_be_recorded_against_a_run_that_never_recovered( ( "running", "mark-failed s1", - "docs/labs/azure-monitor/sre-agent-scenario-http-500/index.md", + "docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md", ), ( "failed", - "docs/labs/azure-monitor/sre-agent-scenario-http-500/index.md", + "docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md", "mark-failed s1", ), ( None, - "docs/labs/azure-monitor/sre-agent-scenario-http-500/index.md", + "docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md", "mark-failed s1", ), ), @@ -1233,7 +1233,7 @@ def test_cli_evidence_dir_without_a_run_names_the_command_to_run(tmp_path): result = run_cli(tmp_path / "state.json", ["evidence-dir", "s1"]) assert result.returncode == 1 - assert "docs/labs/azure-monitor/sre-agent-scenario-http-500/index.md" in result.stderr + assert "docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md" in result.stderr assert "Traceback" not in result.stderr @@ -1307,7 +1307,7 @@ def test_cli_require_run_refuses_every_scenario_while_one_run_is_unfinished(tmp_ assert refused.returncode == 1, refused.stdout assert "s3" in refused.stderr assert "failed" in refused.stderr - assert "docs/labs/azure-monitor/sre-agent-scenario-blob-permission/index.md" in refused.stderr + assert "docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md" in refused.stderr assert "Traceback" not in refused.stderr assert run_cli(path, ["require-run", "s3"]).returncode == 0 diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_loadgen.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_loadgen.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_loadgen.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_loadgen.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_notifications.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_notifications.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_notifications.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_notifications.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_privacy.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_privacy.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_privacy.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_privacy.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_query_evidence.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_query_evidence.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_query_evidence.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_query_evidence.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_render_capture.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_render_capture.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_render_capture.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_render_capture.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_repo_devcontainer.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_repo_devcontainer.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_repo_devcontainer.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_repo_devcontainer.py diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_repo_readme.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_repo_readme.py similarity index 89% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_repo_readme.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_repo_readme.py index 1995068..7579ec4 100644 --- a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_repo_readme.py +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_repo_readme.py @@ -4,7 +4,7 @@ ROOT_README = REPO_ROOT / "README.md" -SAMPLE_ROOT = "samples/azure-monitor/source-material/sre-agent-event-lab" +SAMPLE_ROOT = "docs/services/azure-monitor/azure-sre-agent/samples/event-lab" def test_root_readme_routes_readers_to_the_searchable_pages_site(): diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_score.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_score.py similarity index 99% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_score.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_score.py index 98c99cf..e58f3af 100644 --- a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_score.py +++ b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_score.py @@ -412,5 +412,5 @@ def test_cli_without_any_state_explains_what_to_run_first(tmp_path): result = run_cli(tmp_path) assert result.returncode == 1 - assert "docs/labs/azure-monitor/sre-agent-scenario-http-500/index.md" in result.stderr + assert "docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md" in result.stderr assert "Traceback" not in result.stderr diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_setup_venv.py b/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_setup_venv.py similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/test_setup_venv.py rename to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_setup_venv.py diff --git a/docs/labs/azure-monitor/sre-agent-scenario-blob-permission/index.md b/docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md similarity index 97% rename from docs/labs/azure-monitor/sre-agent-scenario-blob-permission/index.md rename to docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md index c1158e4..83362ef 100644 --- a/docs/labs/azure-monitor/sre-agent-scenario-blob-permission/index.md +++ b/docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md @@ -21,6 +21,9 @@ tags: - ai-agents - authorization - monitoring +topic_order: 5 +redirect_from: +- labs/azure-monitor/sre-agent-scenario-blob-permission/index.md --- # 04. S3 — Blob 권한 제거 장애 @@ -29,7 +32,7 @@ tags: ## 시작 조건 -- [S2 — 응답 지연](../sre-agent-scenario-latency/index.md)의 S2가 복구되고 캡처가 `conclusion`으로 끝났습니다. +- [S2 — 응답 지연](../scenario-latency/index.md)의 S2가 복구되고 캡처가 `conclusion`으로 끝났습니다. - `evidence/state.json`에 `s2_recovered`와 `s2_captured`가 있습니다. - 다른 시나리오의 실행이 `running`이나 `failed`로 남아 있지 않습니다. S1을 다시 돌리다 실패한 채로 두면 S2 기록이 멀쩡해도 S3는 거부됩니다. - 역할 할당을 만들고 지울 권한이 그대로 있습니다. @@ -41,7 +44,7 @@ tags: Codespaces에서 이 저장소를 열었다면 `az login`을 마친 뒤 아래 한 줄로 이번 실습에 필요한 값이 모두 셸에 준비됩니다. 여기서부터 마지막 `record-capture`까지는 같은 셸에서 실행합니다. ```bash -cd samples/azure-monitor/source-material/sre-agent-event-lab +cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab source ./scripts/lab-env.sh ``` @@ -315,4 +318,4 @@ az role assignment create \ ## 다음 단계 -수집한 근거를 채점합니다: [결과 채점](../sre-agent-results/index.md) +수집한 근거를 채점합니다: [결과 채점](../results/index.md) diff --git a/docs/labs/azure-monitor/sre-agent-scenario-http-500/index.md b/docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md similarity index 95% rename from docs/labs/azure-monitor/sre-agent-scenario-http-500/index.md rename to docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md index 6693443..8db25a9 100644 --- a/docs/labs/azure-monitor/sre-agent-scenario-http-500/index.md +++ b/docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md @@ -21,6 +21,9 @@ tags: - ai-agents - troubleshooting - monitoring +topic_order: 3 +redirect_from: +- labs/azure-monitor/sre-agent-scenario-http-500/index.md --- # 02. S1 — HTTP 500 장애 @@ -29,7 +32,7 @@ tags: ## 시작 조건 -- [에이전트 설정](../sre-agent-event-lab-setup/index.md)을 마쳤고 `evidence/state.json`에 `baseline_passed`와 `agent_setup_acknowledged`가 기록되어 있습니다. +- [에이전트 설정](../setup/index.md)을 마쳤고 `evidence/state.json`에 `baseline_passed`와 `agent_setup_acknowledged`가 기록되어 있습니다. - 현재 활성 구독이 azd 환경의 구독과 같습니다. 이 두 가지는 `evidence/state.json`을 통해 강제됩니다. 아래 "수동 실행"의 첫 단계인 `lab_state.py begin-run`이 순서와 중복 실행을 함께 확인합니다. @@ -45,7 +48,7 @@ tags: Codespaces에서 이 저장소를 열었다면 `az login`을 마친 뒤 아래 한 줄로 이번 실습에 필요한 값이 모두 셸에 준비됩니다. 여기서부터 마지막 `record-capture`까지는 같은 셸에서 실행합니다. ```bash -cd samples/azure-monitor/source-material/sre-agent-event-lab +cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab source ./scripts/lab-env.sh ``` @@ -349,9 +352,9 @@ fi | `conclusion-missing` | 조사는 했지만 결론에 도달하지 못했습니다 | 막힙니다 | | `thread-not-created` | 경고가 Agent에 도달하지 못했습니다 | 막힙니다 | -성공은 `conclusion` 하나뿐입니다. 부분 성공은 결론이 나왔지만 내용이 얕은 경우이며, 이때도 상태는 `conclusion`이고 점수는 [결과 채점](../sre-agent-results/index.md)에서 갈립니다. 빈 성공 화면을 만들지 않고 누락 상태와 마지막 확인 시각을 그대로 그림에 남깁니다. +성공은 `conclusion` 하나뿐입니다. 부분 성공은 결론이 나왔지만 내용이 얕은 경우이며, 이때도 상태는 `conclusion`이고 점수는 [결과 채점](../results/index.md)에서 갈립니다. 빈 성공 화면을 만들지 않고 누락 상태와 마지막 확인 시각을 그대로 그림에 남깁니다. -`thread-not-created`가 나오면 [에이전트 설정](../sre-agent-event-lab-setup/index.md)의 incident platform과 응답 계획부터 다시 확인합니다. +`thread-not-created`가 나오면 [에이전트 설정](../setup/index.md)의 incident platform과 응답 계획부터 다시 확인합니다. ## 복구 확인 @@ -372,4 +375,4 @@ azd env get-value AZURE_CONTAINER_APP_FQDN ## 다음 단계 -지연 장애로 넘어갑니다: [S2 — 응답 지연](../sre-agent-scenario-latency/index.md) +지연 장애로 넘어갑니다: [S2 — 응답 지연](../scenario-latency/index.md) diff --git a/docs/labs/azure-monitor/sre-agent-scenario-latency/index.md b/docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md similarity index 97% rename from docs/labs/azure-monitor/sre-agent-scenario-latency/index.md rename to docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md index 2b505e2..50925b2 100644 --- a/docs/labs/azure-monitor/sre-agent-scenario-latency/index.md +++ b/docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md @@ -21,6 +21,9 @@ tags: - ai-agents - latency - monitoring +topic_order: 4 +redirect_from: +- labs/azure-monitor/sre-agent-scenario-latency/index.md --- # 03. S2 — 응답 지연 장애 @@ -29,7 +32,7 @@ tags: ## 시작 조건 -- [S1 — HTTP 500 장애](../sre-agent-scenario-http-500/index.md)의 S1이 복구되고 캡처가 `conclusion`으로 끝났습니다. +- [S1 — HTTP 500 장애](../scenario-http-500/index.md)의 S1이 복구되고 캡처가 `conclusion`으로 끝났습니다. - `evidence/state.json`에 `s1_recovered`와 `s1_captured`가 있습니다. - 다른 시나리오의 실행이 `running`이나 `failed`로 남아 있지 않습니다. 하나라도 남아 있으면 S2도 거부되고, 거부 메시지가 막고 있는 시나리오와 해결 명령을 알려 줍니다. - 워크로드가 정상이고 S1 경고가 해제되어 있습니다. @@ -41,7 +44,7 @@ S1과 같은 구조이며, 바뀌는 것은 주입 값과 부하 조건뿐입니 Codespaces에서 이 저장소를 열었다면 `az login`을 마친 뒤 아래 한 줄로 이번 실습에 필요한 값이 모두 셸에 준비됩니다. 여기서부터 마지막 `record-capture`까지는 같은 셸에서 실행합니다. ```bash -cd samples/azure-monitor/source-material/sre-agent-event-lab +cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab source ./scripts/lab-env.sh ``` @@ -325,4 +328,4 @@ fi ## 다음 단계 -권한 장애로 넘어갑니다: [S3 — Blob 권한 장애](../sre-agent-scenario-blob-permission/index.md) +권한 장애로 넘어갑니다: [S3 — Blob 권한 장애](../scenario-blob-permission/index.md) diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/portal-complete-setup-page.png b/docs/services/azure-monitor/azure-sre-agent/setup/images/portal-complete-setup-page.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/portal-complete-setup-page.png rename to docs/services/azure-monitor/azure-sre-agent/setup/images/portal-complete-setup-page.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/portal-incident-response-plans-list.png b/docs/services/azure-monitor/azure-sre-agent/setup/images/portal-incident-response-plans-list.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/portal-incident-response-plans-list.png rename to docs/services/azure-monitor/azure-sre-agent/setup/images/portal-incident-response-plans-list.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/portal-response-plan-autonomy-step.png b/docs/services/azure-monitor/azure-sre-agent/setup/images/portal-response-plan-autonomy-step.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/portal-response-plan-autonomy-step.png rename to docs/services/azure-monitor/azure-sre-agent/setup/images/portal-response-plan-autonomy-step.png diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/portal-setup-status-bar.png b/docs/services/azure-monitor/azure-sre-agent/setup/images/portal-setup-status-bar.png similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/official/portal-setup-status-bar.png rename to docs/services/azure-monitor/azure-sre-agent/setup/images/portal-setup-status-bar.png diff --git a/docs/labs/azure-monitor/sre-agent-event-lab-setup/index.md b/docs/services/azure-monitor/azure-sre-agent/setup/index.md similarity index 96% rename from docs/labs/azure-monitor/sre-agent-event-lab-setup/index.md rename to docs/services/azure-monitor/azure-sre-agent/setup/index.md index 14c2e43..7fde52e 100644 --- a/docs/labs/azure-monitor/sre-agent-event-lab-setup/index.md +++ b/docs/services/azure-monitor/azure-sre-agent/setup/index.md @@ -21,6 +21,9 @@ tags: - ai-agents - deployment - monitoring +topic_order: 2 +redirect_from: +- labs/azure-monitor/sre-agent-event-lab-setup/index.md --- # 01. Azure SRE Agent 설정 @@ -30,13 +33,13 @@ tags: ## 시작 조건 - **이 저장소를 본인 계정으로 fork했습니다.** Agent에 연결하는 저장소는 fork여야 합니다. 아래 "연결할 원본"에서 설명하듯 조사 결과 이슈가 연결된 저장소에 생성되므로, 원본 저장소를 연결하면 참가자 전원의 이슈가 한곳에 쌓입니다. -- [README](../sre-agent-event-lab/index.md)의 `azd up`이 성공했고 `/healthz`가 HTTP 200을 반환합니다. +- [README](../event-lab/index.md)의 `azd up`이 성공했고 `/healthz`가 HTTP 200을 반환합니다. - `https://sre.azure.com`에 로그인할 수 있고, 대상 구독에 Azure SRE Agent가 **Running** 상태로 하나 있습니다. - 역할을 만들 수 있는 권한(Owner 또는 User Access Administrator)이 있습니다. - 아래 값을 손에 들고 시작합니다. ```bash -cd samples/azure-monitor/source-material/sre-agent-event-lab +cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab source ./scripts/lab-env.sh ``` @@ -62,7 +65,7 @@ Agent를 열면 위쪽 상태 표시줄이 아직 연결하지 않은 데이터 |---|---|---| | Code | **본인이 fork한 저장소**와 브랜치 (`source ./scripts/lab-env.sh`가 출력한 `Repository` 값) | 조사 결론이 코드와 최근 변경을 짚게 합니다 | | Azure resources | `lab-env.sh`가 출력한 `Resource group` 값 | 메트릭·리소스 상태·Activity Log를 읽습니다 | -| Knowledge files | [인시던트 대응 런북](../../../guides/azure-monitor/sre-agent-incident-runbook/index.md) | 조사 순서와 금지 사항을 팀 규칙으로 강제합니다 | +| Knowledge files | [인시던트 대응 런북](../incident-runbook/index.md) | 조사 순서와 금지 사항을 팀 규칙으로 강제합니다 | | Incidents | Azure Monitor | 경고를 자동으로 받아 조사 스레드를 엽니다 | 저장소는 [Connect source code](https://learn.microsoft.com/azure/sre-agent/connect-source-code)로 연결합니다. 이슈·PR 조작까지 맡기려면 [GitHub connector](https://learn.microsoft.com/azure/sre-agent/setup-github-connector)를 추가로 설정하되, 토큰 값은 포털 입력창에만 넣고 이 저장소의 어떤 파일에도 남기지 않습니다. @@ -261,4 +264,4 @@ python3 scripts/lab_state.py acknowledge-agent ## 다음 단계 -첫 장애를 주입합니다: [S1 — HTTP 500 장애](../sre-agent-scenario-http-500/index.md) +첫 장애를 주입합니다: [S1 — HTTP 500 장애](../scenario-http-500/index.md) diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1-before-bridge/investigation.gif b/docs/services/azure-monitor/azure-sre-agent/validation-results/images/investigation.gif similarity index 100% rename from samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1-before-bridge/investigation.gif rename to docs/services/azure-monitor/azure-sre-agent/validation-results/images/investigation.gif diff --git a/docs/research/azure-monitor/sre-agent-validation-results/images/s1-investigation.gif b/docs/services/azure-monitor/azure-sre-agent/validation-results/images/s1-investigation.gif similarity index 100% rename from docs/research/azure-monitor/sre-agent-validation-results/images/s1-investigation.gif rename to docs/services/azure-monitor/azure-sre-agent/validation-results/images/s1-investigation.gif diff --git a/docs/research/azure-monitor/sre-agent-validation-results/images/s2-investigation.gif b/docs/services/azure-monitor/azure-sre-agent/validation-results/images/s2-investigation.gif similarity index 100% rename from docs/research/azure-monitor/sre-agent-validation-results/images/s2-investigation.gif rename to docs/services/azure-monitor/azure-sre-agent/validation-results/images/s2-investigation.gif diff --git a/docs/research/azure-monitor/sre-agent-validation-results/images/s3-investigation.gif b/docs/services/azure-monitor/azure-sre-agent/validation-results/images/s3-investigation.gif similarity index 100% rename from docs/research/azure-monitor/sre-agent-validation-results/images/s3-investigation.gif rename to docs/services/azure-monitor/azure-sre-agent/validation-results/images/s3-investigation.gif diff --git a/docs/research/azure-monitor/sre-agent-validation-results/index.md b/docs/services/azure-monitor/azure-sre-agent/validation-results/index.md similarity index 92% rename from docs/research/azure-monitor/sre-agent-validation-results/index.md rename to docs/services/azure-monitor/azure-sre-agent/validation-results/index.md index 0a11046..dd79e2d 100644 --- a/docs/research/azure-monitor/sre-agent-validation-results/index.md +++ b/docs/services/azure-monitor/azure-sre-agent/validation-results/index.md @@ -17,15 +17,18 @@ tags: - monitoring - diagnostics published_at: 2026-08-15 +topic_order: 7 +redirect_from: +- research/azure-monitor/sre-agent-validation-results/index.md --- # Azure SRE Agent 실제 동작 검증 결과 -> 제품 개요와 실사용 패턴은 [Azure SRE Agent 소개 자료](../../../guides/azure-monitor/azure-sre-agent-overview/index.md)를 먼저 참고한다. +> 제품 개요와 실사용 패턴은 [Azure SRE Agent 소개 자료](../index.md)를 먼저 참고한다. > > 이 문서는 S1/S2/S3 시나리오에서 측정한 수치, timeline, evidence, 한계를 정리한다. > -> **기록 시점 주의.** 아래 결과는 azd 재구성 이전에 손으로 구축한 실습(2026-08-12)의 측정치다. 여기 적힌 `rg-sre-agent-event-lab-krc` 같은 리소스 그룹과 리소스 이름, Action Group + Logic App bridge는 모두 그때의 환경이고, 현재 실습의 `azd up`은 이 이름들을 만들지 않는다. 지금 실행하는 절차와 실제로 배포되는 구성은 [현재 실습 문서](../../../labs/azure-monitor/sre-agent-event-lab/index.md)를 따르고, 이 문서는 그 절차로 무엇을 관찰할 수 있었는지 보여 주는 과거 기록으로 읽는다. +> **기록 시점 주의.** 아래 결과는 azd 재구성 이전에 손으로 구축한 실습(2026-08-12)의 측정치다. 여기 적힌 `rg-sre-agent-event-lab-krc` 같은 리소스 그룹과 리소스 이름, Action Group + Logic App bridge는 모두 그때의 환경이고, 현재 실습의 `azd up`은 이 이름들을 만들지 않는다. 지금 실행하는 절차와 실제로 배포되는 구성은 [현재 실습 문서](../event-lab/index.md)를 따르고, 이 문서는 그 절차로 무엇을 관찰할 수 있었는지 보여 주는 과거 기록으로 읽는다. - 실행일: 2026-08-12 | 리전: Korea Central | 구독: 비식별화 - 목표: Azure Monitor 경고를 Azure SRE Agent가 자동 수신해 원인과 안전한 완화책을 올바르게 도출하는지 실증 @@ -137,9 +140,9 @@ published_at: 2026-08-15 ![S1 SRE Agent investigation](images/s1-investigation.gif) -- [결론 frame](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/07-conclusion.png) -- [실제 event timeline](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/timeline.md) -- [Mermaid sequence source](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s1/timeline.mmd) +- [결론 frame](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/07-conclusion.png) +- [실제 event timeline](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/timeline.md) +- [Mermaid sequence source](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/timeline.mmd) - 원본 evidence: `monitor/sre-agent-event-lab/evidence/s1-20260812T080606Z/` (Git 제외) - Agent thread: `` @@ -179,9 +182,9 @@ published_at: 2026-08-15 ![S2 SRE Agent investigation](images/s2-investigation.gif) -- [결론 frame](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/07-conclusion.png) -- [실제 event timeline](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/timeline.md) -- [Mermaid sequence source](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s2/timeline.mmd) +- [결론 frame](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/07-conclusion.png) +- [실제 event timeline](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/timeline.md) +- [Mermaid sequence source](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/timeline.mmd) - 원본 evidence: `monitor/sre-agent-event-lab/evidence/s2-20260812T081539Z/` (Git 제외) - Agent thread: `` @@ -221,9 +224,9 @@ Container App workload identity의 테스트 Blob container data-plane read 역 ![S3 SRE Agent investigation](images/s3-investigation.gif) -- [결론 frame](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/07-conclusion.png) -- [실제 event timeline](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/timeline.md) -- [Mermaid sequence source](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/sre-agent-event-lab/assets/captures/s3/timeline.mmd) +- [결론 frame](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/07-conclusion.png) +- [실제 event timeline](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.md) +- [Mermaid sequence source](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.mmd) - 원본 evidence: `monitor/sre-agent-event-lab/evidence/s3-20260812T084004Z/` (Git 제외) - Agent thread: `` diff --git a/docs/research/azure-monitor/dynamic-thresholds-brief/images/dynamic-threshold-preview-chart.png b/docs/services/azure-monitor/dynamic-thresholds-brief/images/dynamic-threshold-preview-chart.png similarity index 100% rename from docs/research/azure-monitor/dynamic-thresholds-brief/images/dynamic-threshold-preview-chart.png rename to docs/services/azure-monitor/dynamic-thresholds-brief/images/dynamic-threshold-preview-chart.png diff --git a/docs/research/azure-monitor/dynamic-thresholds-brief/index.md b/docs/services/azure-monitor/dynamic-thresholds-brief/index.md similarity index 98% rename from docs/research/azure-monitor/dynamic-thresholds-brief/index.md rename to docs/services/azure-monitor/dynamic-thresholds-brief/index.md index 7e213d2..6fb39ee 100644 --- a/docs/research/azure-monitor/dynamic-thresholds-brief/index.md +++ b/docs/services/azure-monitor/dynamic-thresholds-brief/index.md @@ -16,6 +16,8 @@ tags: - monitoring - reliability published_at: 2026-08-17 +redirect_from: +- research/azure-monitor/dynamic-thresholds-brief/index.md --- # Azure Monitor Dynamic Thresholds: What Changes and When to Adopt diff --git a/docs/research/azure-hdinsight/kafka-catchup-sku-fetch-benchmark/index.md b/docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md similarity index 98% rename from docs/research/azure-hdinsight/kafka-catchup-sku-fetch-benchmark/index.md rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md index 77cdf13..4c86b0f 100644 --- a/docs/research/azure-hdinsight/kafka-catchup-sku-fetch-benchmark/index.md +++ b/docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md @@ -1,5 +1,6 @@ --- services: +- azure-monitor - azure-hdinsight official_sources: - title: Azure HDInsight documentation @@ -16,6 +17,9 @@ tags: - benchmarking - performance published_at: 2026-07-31 +topic_order: 2 +redirect_from: +- research/azure-hdinsight/kafka-catchup-sku-fetch-benchmark/index.md --- # HDInsight Kafka lag/catch-up 벤치마크 — broker SKU / consumer fetch size @@ -285,8 +289,8 @@ az hdinsight delete -g rg-example-japaneast-01 -n hdi-example-japaneast-01 --yes ## 관련 문서 -- [HDInsight Kafka 모니터링 방식 비교](../../azure-monitor/hdinsight-kafka-monitoring-options/index.md) — Azure Monitor·Log Analytics·Ambari·진단 설정 개요. -- [Prometheus + Grafana 대시보드 구성](../../../guides/azure-monitor/hdinsight-kafka-prometheus-grafana/index.md) — broker JMX·kafka-exporter(partition별 lag) 대시보드 구성. +- [HDInsight Kafka 모니터링 방식 비교](../index.md) — Azure Monitor·Log Analytics·Ambari·진단 설정 개요. +- [Prometheus + Grafana 대시보드 구성](../prometheus-grafana/index.md) — broker JMX·kafka-exporter(partition별 lag) 대시보드 구성. --- diff --git a/docs/research/azure-monitor/hdinsight-kafka-monitoring-options/images/hdinsight_monitor_integration_1.png b/docs/services/azure-monitor/hdinsight-kafka-monitoring/images/hdinsight_monitor_integration_1.png similarity index 100% rename from docs/research/azure-monitor/hdinsight-kafka-monitoring-options/images/hdinsight_monitor_integration_1.png rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/images/hdinsight_monitor_integration_1.png diff --git a/docs/research/azure-monitor/hdinsight-kafka-monitoring-options/index.md b/docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md similarity index 98% rename from docs/research/azure-monitor/hdinsight-kafka-monitoring-options/index.md rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md index a446f30..d9433ee 100644 --- a/docs/research/azure-monitor/hdinsight-kafka-monitoring-options/index.md +++ b/docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md @@ -17,6 +17,8 @@ tags: - monitoring - observability published_at: 2026-07-10 +redirect_from: +- research/azure-monitor/hdinsight-kafka-monitoring-options/index.md --- # HDInsight Kafka 모니터링 방식 비교 (Ambari / Monitor Integration / JMX Exporter / Kafka Exporter) @@ -28,7 +30,7 @@ published_at: 2026-07-10 > - **노드 리소스 · 클러스터 상태 · 장기 로그** → Ambari + Monitor Integration (HDInsight 기본, 추가 설치 없음) > - **브로커/토픽 처리량 · JVM internals · Consumer Group Lag** → HDInsight 기본 도구 **관측 불가** > - → **JMX Exporter + Kafka Exporter (+Prometheus/Grafana)** 도입 권장 -> - 설치·대시보드 구성: **[Prometheus + Grafana 구성 가이드](../../../guides/azure-monitor/hdinsight-kafka-prometheus-grafana/index.md)** +> - 설치·대시보드 구성: **[Prometheus + Grafana 구성 가이드](prometheus-grafana/index.md)** ## 1. 관측 항목 → 조회 도구 매핑 @@ -98,7 +100,7 @@ O = 기본 제공 / △ = 조건부·간접 제공 / X = 제공 안 함 ### JMX Exporter / Kafka Exporter (오픈소스, 직접 구성) — 권장 HDInsight 기본 미제공 → 수동 설치. 둘 다 **Prometheus 스크래핑 + Grafana 시각화** 조합. -설치·대시보드: **[Prometheus + Grafana 구성 가이드](../../../guides/azure-monitor/hdinsight-kafka-prometheus-grafana/index.md)** +설치·대시보드: **[Prometheus + Grafana 구성 가이드](prometheus-grafana/index.md)** - **JMX Exporter** — 브로커 JVM MBean을 Prometheus 형식으로 노출. **JVM internals**(Heap/GC/스레드) + **브로커/토픽/파티션 지표**(BytesIn/Out, Under-Replicated Partitions 등). @@ -238,7 +240,7 @@ HDInsightKafkaMetrics > **권장 목표 상태**: Monitor Integration으로 노드/클러스터 baseline 확보 + > **브로커/토픽 상세·JVM internals·Consumer Lag은 JMX/Kafka Exporter로 관측**. -> → 설치·대시보드: **[Prometheus + Grafana 구성 가이드](../../../guides/azure-monitor/hdinsight-kafka-prometheus-grafana/index.md)** +> → 설치·대시보드: **[Prometheus + Grafana 구성 가이드](prometheus-grafana/index.md)** --- diff --git a/docs/guides/azure-monitor/hdinsight-kafka-prometheus-grafana/images/kafka-exporter-adv.png b/docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/images/kafka-exporter-adv.png similarity index 100% rename from docs/guides/azure-monitor/hdinsight-kafka-prometheus-grafana/images/kafka-exporter-adv.png rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/images/kafka-exporter-adv.png diff --git a/docs/guides/azure-monitor/hdinsight-kafka-prometheus-grafana/images/kafka-exporter-overview.png b/docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/images/kafka-exporter-overview.png similarity index 100% rename from docs/guides/azure-monitor/hdinsight-kafka-prometheus-grafana/images/kafka-exporter-overview.png rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/images/kafka-exporter-overview.png diff --git a/docs/guides/azure-monitor/hdinsight-kafka-prometheus-grafana/images/kafka-jmx-broker.png b/docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/images/kafka-jmx-broker.png similarity index 100% rename from docs/guides/azure-monitor/hdinsight-kafka-prometheus-grafana/images/kafka-jmx-broker.png rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/images/kafka-jmx-broker.png diff --git a/docs/guides/azure-monitor/hdinsight-kafka-prometheus-grafana/index.md b/docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md similarity index 84% rename from docs/guides/azure-monitor/hdinsight-kafka-prometheus-grafana/index.md rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md index 980ff8e..7d27f1c 100644 --- a/docs/guides/azure-monitor/hdinsight-kafka-prometheus-grafana/index.md +++ b/docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md @@ -22,18 +22,21 @@ tags: - monitoring - observability - deployment +topic_order: 1 +redirect_from: +- guides/azure-monitor/hdinsight-kafka-prometheus-grafana/index.md --- # HDInsight Kafka JMX Exporter / Kafka Exporter 설치 및 Grafana 대시보드 구성 **전제: HDInsight Kafka 클러스터 + Prometheus + Grafana 기구성.** -도구별 커버 범위·선택 근거("Why") → [HDInsight Kafka 모니터링 방식 비교](../../../research/azure-monitor/hdinsight-kafka-monitoring-options/index.md). +도구별 커버 범위·선택 근거("Why") → [HDInsight Kafka 모니터링 방식 비교](../index.md). > **문서 성격**: JMX Exporter·Kafka Exporter의 정식 사용법은 각 오픈소스 공식 문서([9절 출처](#9)) 기준. > 이 문서는 그 위에서 **HDInsight 환경(원격 JMX 9999, Private Link 등)에 맞춰 실전 구성한 요약본**. > 아래 Grafana 대시보드도 커뮤니티 대시보드(7589) 외에는 **본 환경 지표에 맞춰 개별 구성**한 것 → 참고용, 환경별 조정 필요. -참조 설정·대시보드 파일: [`assets/prometheus-grafana/`](https://github.com/hellices/devguidesample/tree/main/samples/azure-monitor/source-material/assets/prometheus-grafana) +참조 설정·대시보드 파일: [`assets/prometheus-grafana/`](https://github.com/hellices/devguidesample/tree/main/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana) ``` assets/prometheus-grafana/ @@ -122,9 +125,9 @@ for i in 0 1 2; do done ``` -- 변환 룰: [`jmx/kafka-rules.yml`](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/assets/prometheus-grafana/jmx/kafka-rules.yml) +- 변환 룰: [`jmx/kafka-rules.yml`](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/jmx/kafka-rules.yml) (처리량 BytesIn/Out·MessagesIn, ReplicaManager URP/ISR, Controller, RequestMetrics 지연, JVM Heap/GC/Thread 등 16개) -- `hostPort`만 브로커별 상이, 룰 내용 동일. 예시: [`jmx/b0.yml.example`](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/assets/prometheus-grafana/jmx/b0.yml.example) +- `hostPort`만 브로커별 상이, 룰 내용 동일. 예시: [`jmx/b0.yml.example`](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/jmx/b0.yml.example) ### 3.2 Kafka Exporter (Consumer Lag) @@ -145,7 +148,7 @@ kafka-exporter: ### 3.3 일괄 기동 (docker-compose) -두 Exporter + Prometheus + Grafana 일괄 기동: [`docker-compose.yml`](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/assets/prometheus-grafana/docker-compose.yml). +두 Exporter + Prometheus + Grafana 일괄 기동: [`docker-compose.yml`](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/docker-compose.yml). ``, `` 실제 값 치환 후: ```bash @@ -156,7 +159,7 @@ docker compose ps # kafka-exporter, jmx-b0/b1/b2, prometheus, grafana = U ### 3.4 Prometheus 스크래핑 -[`prometheus.yml`](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/assets/prometheus-grafana/prometheus.yml): `kafka-exporter:9308` + +[`prometheus.yml`](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/prometheus.yml): `kafka-exporter:9308` + `jmx-b0/b1/b2:9404` 스크래핑. JMX 잡에 `broker` 라벨 부여 → 브로커 구분. **기존 Prometheus 존재 시** 이 `scrape_configs` 두 블록만 추가 후 reload. @@ -168,8 +171,8 @@ docker compose ps # kafka-exporter, jmx-b0/b1/b2, prometheus, grafana = U docker-compose가 Grafana에 자동 주입: -- 데이터소스: [`grafana/provisioning/datasources/prometheus.yml`](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/assets/prometheus-grafana/grafana/provisioning/datasources/prometheus.yml) (uid `prometheus`) -- 대시보드 폴더: [`grafana/provisioning/dashboards/provider.yml`](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/assets/prometheus-grafana/grafana/provisioning/dashboards/provider.yml) → `Kafka` 폴더 +- 데이터소스: [`grafana/provisioning/datasources/prometheus.yml`](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/provisioning/datasources/prometheus.yml) (uid `prometheus`) +- 대시보드 폴더: [`grafana/provisioning/dashboards/provider.yml`](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/provisioning/dashboards/provider.yml) → `Kafka` 폴더 - `grafana/dashboards/*.json` 자동 로드 **기존 Grafana 존재 시** 아래 대시보드 Dashboards → Import + 데이터소스 지정. @@ -179,8 +182,8 @@ docker-compose가 Grafana에 자동 주입: | 대시보드 | 출처 | 지표 | |----------|------|---------| | **Kafka Exporter Overview** | Grafana.com **7589** (공식 커뮤니티) | 메시지 생산율, Consumer Group Lag, 토픽 파티션 수 | -| **HDInsight Kafka - Exporter 상세** | 커스텀 ([kafka-exporter-adv.json](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/assets/prometheus-grafana/grafana/dashboards/kafka-exporter-adv.json)) | 컨슈머 그룹 **멤버 수(0=컨슈머 다운)**, Lag 상위 파티션, 언더리플리케이션·ISR·브로커별 리더 분포, 토픽별 보관 메시지 수 | -| **HDInsight Kafka - Broker (JMX Exporter)** | 커스텀 ([kafka-jmx-broker.json](https://github.com/hellices/devguidesample/blob/main/samples/azure-monitor/source-material/assets/prometheus-grafana/grafana/dashboards/kafka-jmx-broker.json)) | 브로커별 처리량(BytesIn/Out·MessagesIn), URP, ActiveController, 요청 지연, JVM Heap·스레드 | +| **HDInsight Kafka - Exporter 상세** | 커스텀 ([kafka-exporter-adv.json](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/dashboards/kafka-exporter-adv.json)) | 컨슈머 그룹 **멤버 수(0=컨슈머 다운)**, Lag 상위 파티션, 언더리플리케이션·ISR·브로커별 리더 분포, 토픽별 보관 메시지 수 | +| **HDInsight Kafka - Broker (JMX Exporter)** | 커스텀 ([kafka-jmx-broker.json](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/dashboards/kafka-jmx-broker.json)) | 브로커별 처리량(BytesIn/Out·MessagesIn), URP, ActiveController, 요청 지연, JVM Heap·스레드 | **Kafka Exporter Overview(7589)** = 커뮤니티 대시보드 → 파일 아닌 ID로 import. Grafana UI → Dashboards → **Import** → ID `7589` → 데이터소스 `Prometheus`. @@ -204,7 +207,7 @@ Prometheus(`:9090`) 쿼리로 확인. ## 6. 대시보드 화면 -> 렌더링 이미지는 이 문서 번들의 `images/` 디렉터리에 아래 파일명으로 두면 자동 표시됩니다. 원본 자료는 [`samples/azure-monitor/source-material/assets/images/`](https://github.com/hellices/devguidesample/tree/main/samples/azure-monitor/source-material/assets/images)에서 확인할 수 있습니다. +> 렌더링 이미지는 이 문서 번들의 `images/` 디렉터리에 아래 파일명으로 두면 자동 표시됩니다. 원본 자료는 [`samples/monitoring-assets/images/`](https://github.com/hellices/devguidesample/tree/main/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images)에서 확인할 수 있습니다. ### 6.1 Kafka Exporter Overview (7589) diff --git a/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/deployment/README.md b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/deployment/README.md new file mode 100644 index 0000000..6f47e71 --- /dev/null +++ b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/deployment/README.md @@ -0,0 +1,4 @@ +# HDInsight Kafka deployment artifacts + +This artifact sample contains the Bicep templates and generated template used +for the HDInsight Kafka monitoring environment. diff --git a/samples/infrastructure/bicep/main.bicep b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/deployment/main.bicep similarity index 100% rename from samples/infrastructure/bicep/main.bicep rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/deployment/main.bicep diff --git a/samples/infrastructure/bicep/main.bicepparam b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/deployment/main.bicepparam similarity index 100% rename from samples/infrastructure/bicep/main.bicepparam rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/deployment/main.bicepparam diff --git a/samples/infrastructure/bicep/main.json b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/deployment/main.json similarity index 100% rename from samples/infrastructure/bicep/main.json rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/deployment/main.json diff --git a/samples/infrastructure/bicep/modules/monitoring.bicep b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/deployment/modules/monitoring.bicep similarity index 100% rename from samples/infrastructure/bicep/modules/monitoring.bicep rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/deployment/modules/monitoring.bicep diff --git a/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/deployment/sample.yml b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/deployment/sample.yml new file mode 100644 index 0000000..990a79c --- /dev/null +++ b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/deployment/sample.yml @@ -0,0 +1,7 @@ +title: HDInsight Kafka deployment artifacts +description: Bicep templates used by the HDInsight Kafka monitoring topic. +kind: artifact +used_by: +- index +- prometheus-grafana +- catch-up-benchmark diff --git a/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/README.md b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/README.md new file mode 100644 index 0000000..db0d880 --- /dev/null +++ b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/README.md @@ -0,0 +1,4 @@ +# HDInsight Kafka monitoring assets + +This artifact sample contains Prometheus and Grafana configuration, Azure +Monitor templates, dashboards, and reference images for the monitoring topic. diff --git a/samples/azure-monitor/source-material/assets/hdinsight-kafka-dashboard.properties.template.json b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/hdinsight-kafka-dashboard.properties.template.json similarity index 100% rename from samples/azure-monitor/source-material/assets/hdinsight-kafka-dashboard.properties.template.json rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/hdinsight-kafka-dashboard.properties.template.json diff --git a/samples/azure-monitor/source-material/assets/hdinsight-kafka-workbook.template.json b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/hdinsight-kafka-workbook.template.json similarity index 100% rename from samples/azure-monitor/source-material/assets/hdinsight-kafka-workbook.template.json rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/hdinsight-kafka-workbook.template.json diff --git a/samples/azure-monitor/source-material/assets/images/hdinsight_monitor_integration_1.png b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images/hdinsight_monitor_integration_1.png similarity index 100% rename from samples/azure-monitor/source-material/assets/images/hdinsight_monitor_integration_1.png rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images/hdinsight_monitor_integration_1.png diff --git a/samples/azure-monitor/source-material/assets/images/kafka-exporter-adv.png b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images/kafka-exporter-adv.png similarity index 100% rename from samples/azure-monitor/source-material/assets/images/kafka-exporter-adv.png rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images/kafka-exporter-adv.png diff --git a/samples/azure-monitor/source-material/assets/images/kafka-exporter-overview.png b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images/kafka-exporter-overview.png similarity index 100% rename from samples/azure-monitor/source-material/assets/images/kafka-exporter-overview.png rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images/kafka-exporter-overview.png diff --git a/samples/azure-monitor/source-material/assets/images/kafka-jmx-broker.png b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images/kafka-jmx-broker.png similarity index 100% rename from samples/azure-monitor/source-material/assets/images/kafka-jmx-broker.png rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images/kafka-jmx-broker.png diff --git a/samples/azure-monitor/source-material/assets/prometheus-grafana/docker-compose.yml b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/docker-compose.yml similarity index 100% rename from samples/azure-monitor/source-material/assets/prometheus-grafana/docker-compose.yml rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/docker-compose.yml diff --git a/samples/azure-monitor/source-material/assets/prometheus-grafana/grafana/dashboards/kafka-exporter-adv.json b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/dashboards/kafka-exporter-adv.json similarity index 100% rename from samples/azure-monitor/source-material/assets/prometheus-grafana/grafana/dashboards/kafka-exporter-adv.json rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/dashboards/kafka-exporter-adv.json diff --git a/samples/azure-monitor/source-material/assets/prometheus-grafana/grafana/dashboards/kafka-jmx-broker.json b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/dashboards/kafka-jmx-broker.json similarity index 100% rename from samples/azure-monitor/source-material/assets/prometheus-grafana/grafana/dashboards/kafka-jmx-broker.json rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/dashboards/kafka-jmx-broker.json diff --git a/samples/azure-monitor/source-material/assets/prometheus-grafana/grafana/provisioning/dashboards/provider.yml b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/provisioning/dashboards/provider.yml similarity index 100% rename from samples/azure-monitor/source-material/assets/prometheus-grafana/grafana/provisioning/dashboards/provider.yml rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/provisioning/dashboards/provider.yml diff --git a/samples/azure-monitor/source-material/assets/prometheus-grafana/grafana/provisioning/datasources/prometheus.yml b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/provisioning/datasources/prometheus.yml similarity index 100% rename from samples/azure-monitor/source-material/assets/prometheus-grafana/grafana/provisioning/datasources/prometheus.yml rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/provisioning/datasources/prometheus.yml diff --git a/samples/azure-monitor/source-material/assets/prometheus-grafana/jmx/b0.yml.example b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/jmx/b0.yml.example similarity index 100% rename from samples/azure-monitor/source-material/assets/prometheus-grafana/jmx/b0.yml.example rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/jmx/b0.yml.example diff --git a/samples/azure-monitor/source-material/assets/prometheus-grafana/jmx/kafka-rules.yml b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/jmx/kafka-rules.yml similarity index 100% rename from samples/azure-monitor/source-material/assets/prometheus-grafana/jmx/kafka-rules.yml rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/jmx/kafka-rules.yml diff --git a/samples/azure-monitor/source-material/assets/prometheus-grafana/prometheus.yml b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/prometheus.yml similarity index 100% rename from samples/azure-monitor/source-material/assets/prometheus-grafana/prometheus.yml rename to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/prometheus.yml diff --git a/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/sample.yml b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/sample.yml new file mode 100644 index 0000000..1a8aefc --- /dev/null +++ b/docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/sample.yml @@ -0,0 +1,7 @@ +title: HDInsight Kafka monitoring assets +description: Prometheus, Grafana, workbook, dashboard, and reference image assets. +kind: artifact +used_by: +- index +- prometheus-grafana +- catch-up-benchmark diff --git a/docs/guides/azure-openai/adaptive-ptu-load-balancing/images/ptu_architecture.png b/docs/services/azure-openai/adaptive-ptu-load-balancing/images/ptu_architecture.png similarity index 100% rename from docs/guides/azure-openai/adaptive-ptu-load-balancing/images/ptu_architecture.png rename to docs/services/azure-openai/adaptive-ptu-load-balancing/images/ptu_architecture.png diff --git a/docs/guides/azure-openai/adaptive-ptu-load-balancing/index.md b/docs/services/azure-openai/adaptive-ptu-load-balancing/index.md similarity index 95% rename from docs/guides/azure-openai/adaptive-ptu-load-balancing/index.md rename to docs/services/azure-openai/adaptive-ptu-load-balancing/index.md index 5f4fb79..824e335 100644 --- a/docs/guides/azure-openai/adaptive-ptu-load-balancing/index.md +++ b/docs/services/azure-openai/adaptive-ptu-load-balancing/index.md @@ -21,6 +21,8 @@ tags: - performance - reliability - ai-agents +redirect_from: +- guides/azure-openai/adaptive-ptu-load-balancing/index.md --- # Adaptive PTU TPM Quota 조정 가이드 (with Sample Test) @@ -290,12 +292,12 @@ $$ ## 파일 구성 -- 아키텍처 다이어그램(원본): [ptu_architecture.excalidraw](https://github.com/hellices/devguidesample/blob/main/samples/azure-openai/ptu-load-balancing/ptu_architecture.excalidraw) -- 아키텍처 다이어그램(이미지): [ptu_architecture.png](https://github.com/hellices/devguidesample/blob/main/samples/azure-openai/ptu-load-balancing/ptu_architecture.png) -- 다이어그램 렌더러(유틸): [render_excalidraw.py](https://github.com/hellices/devguidesample/blob/main/samples/azure-openai/ptu-load-balancing/render_excalidraw.py) -- **Runbook(기본, Python): [runbooks/adaptive_tpm_quota_controller.py](https://github.com/hellices/devguidesample/blob/main/samples/azure-openai/ptu-load-balancing/runbooks/adaptive_tpm_quota_controller.py)** -- Runbook 의존성: [runbooks/requirements.txt](https://github.com/hellices/devguidesample/blob/main/samples/azure-openai/ptu-load-balancing/runbooks/requirements.txt) -- Runbook(선택, PowerShell 동등 이식본): [runbooks/optional_adaptive_tpm_quota_controller.ps1](https://github.com/hellices/devguidesample/blob/main/samples/azure-openai/ptu-load-balancing/runbooks/optional_adaptive_tpm_quota_controller.ps1) +- 아키텍처 다이어그램(원본): [ptu_architecture.excalidraw](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/ptu_architecture.excalidraw) +- 아키텍처 다이어그램(이미지): [ptu_architecture.png](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/ptu_architecture.png) +- 다이어그램 렌더러(유틸): [render_excalidraw.py](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/render_excalidraw.py) +- **Runbook(기본, Python): [runbooks/adaptive_tpm_quota_controller.py](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/adaptive_tpm_quota_controller.py)** +- Runbook 의존성: [runbooks/requirements.txt](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/requirements.txt) +- Runbook(선택, PowerShell 동등 이식본): [runbooks/optional_adaptive_tpm_quota_controller.ps1](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/optional_adaptive_tpm_quota_controller.ps1) > 기본 구현은 Python 런북이다. PowerShell 버전은 동일한 Reserved 총량 공유 재분배 로직(감축→증설, 실제 actuation·쿨다운 포함)을 Az 모듈(Az.Accounts / Az.CognitiveServices / AzTable)로 이식한 **선택적 대체 구현**으로, PowerShell 선호 환경을 위한 참조본이다. Storage가 Private Endpoint 구성이면 이 런북도 프라이빗 경로가 닿는 워커(예: Windows Hybrid Worker)에서 실행해야 한다. diff --git a/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/README.md b/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/README.md new file mode 100644 index 0000000..d7b1b1e --- /dev/null +++ b/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/README.md @@ -0,0 +1,3 @@ +# PTU LB Test Docs + +메인 문서는 [Adaptive PTU 부하 분산 가이드](../../index.md)입니다. diff --git a/samples/azure-openai/ptu-load-balancing/ptu_architecture.excalidraw b/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/ptu_architecture.excalidraw similarity index 100% rename from samples/azure-openai/ptu-load-balancing/ptu_architecture.excalidraw rename to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/ptu_architecture.excalidraw diff --git a/samples/azure-openai/ptu-load-balancing/ptu_architecture.png b/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/ptu_architecture.png similarity index 100% rename from samples/azure-openai/ptu-load-balancing/ptu_architecture.png rename to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/ptu_architecture.png diff --git a/samples/azure-openai/ptu-load-balancing/render_excalidraw.py b/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/render_excalidraw.py similarity index 100% rename from samples/azure-openai/ptu-load-balancing/render_excalidraw.py rename to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/render_excalidraw.py diff --git a/samples/azure-openai/ptu-load-balancing/runbooks/adaptive_tpm_quota_controller.py b/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/adaptive_tpm_quota_controller.py similarity index 100% rename from samples/azure-openai/ptu-load-balancing/runbooks/adaptive_tpm_quota_controller.py rename to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/adaptive_tpm_quota_controller.py diff --git a/samples/azure-openai/ptu-load-balancing/runbooks/optional_adaptive_tpm_quota_controller.ps1 b/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/optional_adaptive_tpm_quota_controller.ps1 similarity index 100% rename from samples/azure-openai/ptu-load-balancing/runbooks/optional_adaptive_tpm_quota_controller.ps1 rename to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/optional_adaptive_tpm_quota_controller.ps1 diff --git a/samples/azure-openai/ptu-load-balancing/runbooks/requirements.txt b/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/requirements.txt similarity index 100% rename from samples/azure-openai/ptu-load-balancing/runbooks/requirements.txt rename to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/requirements.txt diff --git a/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/sample.yml b/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/sample.yml new file mode 100644 index 0000000..8609b52 --- /dev/null +++ b/docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/sample.yml @@ -0,0 +1,5 @@ +title: Adaptive PTU load-balancing runbooks +description: Runnable quota controllers and architecture assets for PTU balancing. +kind: runnable +used_by: +- index diff --git a/docs/guides/azure-storage/mobile-resumable-upload-tus/images/architecture.png b/docs/services/azure-storage/mobile-resumable-upload-tus/images/architecture.png similarity index 100% rename from docs/guides/azure-storage/mobile-resumable-upload-tus/images/architecture.png rename to docs/services/azure-storage/mobile-resumable-upload-tus/images/architecture.png diff --git a/docs/guides/azure-storage/mobile-resumable-upload-tus/index.md b/docs/services/azure-storage/mobile-resumable-upload-tus/index.md similarity index 96% rename from docs/guides/azure-storage/mobile-resumable-upload-tus/index.md rename to docs/services/azure-storage/mobile-resumable-upload-tus/index.md index eb7d521..3a5e079 100644 --- a/docs/guides/azure-storage/mobile-resumable-upload-tus/index.md +++ b/docs/services/azure-storage/mobile-resumable-upload-tus/index.md @@ -21,6 +21,8 @@ tags: - storage - reliability - development +redirect_from: +- guides/azure-storage/mobile-resumable-upload-tus/index.md --- # Spring Boot Resumable Upload — tus.io ↔ Azure Block Blob @@ -49,9 +51,9 @@ tags: ![Architecture — Mobile (tus 1.0.0) ▸ AKS Pods ▸ Azure Blob](images/architecture.png) -편집 원본: [spring-resumable-upload/architecture.excalidraw](https://github.com/hellices/devguidesample/blob/main/samples/azure-storage/source-material/spring-resumable-upload/architecture.excalidraw) +편집 원본: [spring-application/architecture.excalidraw](https://github.com/hellices/devguidesample/blob/main/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/architecture.excalidraw) -샘플 코드 위치: [spring-resumable-upload/](https://github.com/hellices/devguidesample/tree/main/samples/azure-storage/source-material/spring-resumable-upload) +샘플 코드 위치: [spring-application/](https://github.com/hellices/devguidesample/tree/main/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application) ## 왜 tus 인가 @@ -172,7 +174,7 @@ pip install requests build & run ```bash -cd azureblob/spring-resumable-upload +cd docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application mvn -DskipTests package java -jar target/spring-resumable-upload-0.1.0.jar # or: mvn spring-boot:run @@ -190,7 +192,7 @@ java -jar target/spring-resumable-upload-0.1.0.jar 50 MB 테스트 파일 생성 ```bash -cd spring-resumable-upload/scripts +cd docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/scripts ./make-test-file.sh test-50mb.bin 50 ``` diff --git a/samples/azure-storage/source-material/spring-resumable-upload/.gitignore b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/.gitignore similarity index 100% rename from samples/azure-storage/source-material/spring-resumable-upload/.gitignore rename to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/.gitignore diff --git a/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/README.md b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/README.md new file mode 100644 index 0000000..5ba9d95 --- /dev/null +++ b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/README.md @@ -0,0 +1,4 @@ +# Spring resumable upload application + +This runnable Spring application and its helper scripts demonstrate resumable +uploads to Azure Blob Storage through the tus protocol. diff --git a/samples/azure-storage/source-material/spring-resumable-upload/architecture.excalidraw b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/architecture.excalidraw similarity index 100% rename from samples/azure-storage/source-material/spring-resumable-upload/architecture.excalidraw rename to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/architecture.excalidraw diff --git a/samples/azure-storage/source-material/spring-resumable-upload/architecture.png b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/architecture.png similarity index 100% rename from samples/azure-storage/source-material/spring-resumable-upload/architecture.png rename to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/architecture.png diff --git a/samples/azure-storage/source-material/spring-resumable-upload/pom.xml b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/pom.xml similarity index 100% rename from samples/azure-storage/source-material/spring-resumable-upload/pom.xml rename to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/pom.xml diff --git a/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/sample.yml b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/sample.yml new file mode 100644 index 0000000..49a3dca --- /dev/null +++ b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/sample.yml @@ -0,0 +1,5 @@ +title: Spring resumable upload application +description: Runnable Spring and tus implementation with verification scripts. +kind: runnable +used_by: +- index diff --git a/samples/azure-storage/source-material/spring-resumable-upload/scripts/make-test-file.sh b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/scripts/make-test-file.sh similarity index 100% rename from samples/azure-storage/source-material/spring-resumable-upload/scripts/make-test-file.sh rename to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/scripts/make-test-file.sh diff --git a/samples/azure-storage/source-material/spring-resumable-upload/scripts/tus_client.py b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/scripts/tus_client.py similarity index 100% rename from samples/azure-storage/source-material/spring-resumable-upload/scripts/tus_client.py rename to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/scripts/tus_client.py diff --git a/samples/azure-storage/source-material/spring-resumable-upload/scripts/verify-blob.sh b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/scripts/verify-blob.sh similarity index 100% rename from samples/azure-storage/source-material/spring-resumable-upload/scripts/verify-blob.sh rename to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/scripts/verify-blob.sh diff --git a/samples/azure-storage/source-material/spring-resumable-upload/src/main/java/com/example/upload/UploadApplication.java b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/java/com/example/upload/UploadApplication.java similarity index 100% rename from samples/azure-storage/source-material/spring-resumable-upload/src/main/java/com/example/upload/UploadApplication.java rename to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/java/com/example/upload/UploadApplication.java diff --git a/samples/azure-storage/source-material/spring-resumable-upload/src/main/java/com/example/upload/config/BlobConfig.java b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/java/com/example/upload/config/BlobConfig.java similarity index 100% rename from samples/azure-storage/source-material/spring-resumable-upload/src/main/java/com/example/upload/config/BlobConfig.java rename to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/java/com/example/upload/config/BlobConfig.java diff --git a/samples/azure-storage/source-material/spring-resumable-upload/src/main/java/com/example/upload/tus/TusController.java b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/java/com/example/upload/tus/TusController.java similarity index 100% rename from samples/azure-storage/source-material/spring-resumable-upload/src/main/java/com/example/upload/tus/TusController.java rename to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/java/com/example/upload/tus/TusController.java diff --git a/samples/azure-storage/source-material/spring-resumable-upload/src/main/java/com/example/upload/tus/TusFilter.java b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/java/com/example/upload/tus/TusFilter.java similarity index 100% rename from samples/azure-storage/source-material/spring-resumable-upload/src/main/java/com/example/upload/tus/TusFilter.java rename to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/java/com/example/upload/tus/TusFilter.java diff --git a/samples/azure-storage/source-material/spring-resumable-upload/src/main/java/com/example/upload/tus/TusUploadService.java b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/java/com/example/upload/tus/TusUploadService.java similarity index 100% rename from samples/azure-storage/source-material/spring-resumable-upload/src/main/java/com/example/upload/tus/TusUploadService.java rename to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/java/com/example/upload/tus/TusUploadService.java diff --git a/samples/azure-storage/source-material/spring-resumable-upload/src/main/resources/application.yml b/docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/resources/application.yml similarity index 100% rename from samples/azure-storage/source-material/spring-resumable-upload/src/main/resources/application.yml rename to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/resources/application.yml diff --git a/docs/research/microsoft-foundry/agent-framework-2026/index.md b/docs/services/microsoft-foundry/agent-framework-2026/index.md similarity index 99% rename from docs/research/microsoft-foundry/agent-framework-2026/index.md rename to docs/services/microsoft-foundry/agent-framework-2026/index.md index 960f1b2..2efabf1 100644 --- a/docs/research/microsoft-foundry/agent-framework-2026/index.md +++ b/docs/services/microsoft-foundry/agent-framework-2026/index.md @@ -17,6 +17,8 @@ tags: - ai-agents - architecture published_at: 2026-08-17 +redirect_from: +- research/microsoft-foundry/agent-framework-2026/index.md --- # Microsoft Agent Framework 2026: Python Highlights and the Go Implementation diff --git a/docs/research/microsoft-foundry/agent-memory-architecture-patterns/images/02-architecture-patterns.svg b/docs/services/microsoft-foundry/agent-memory/architecture-patterns/images/02-architecture-patterns.svg similarity index 100% rename from docs/research/microsoft-foundry/agent-memory-architecture-patterns/images/02-architecture-patterns.svg rename to docs/services/microsoft-foundry/agent-memory/architecture-patterns/images/02-architecture-patterns.svg diff --git a/docs/research/microsoft-foundry/agent-memory-architecture-patterns/index.md b/docs/services/microsoft-foundry/agent-memory/architecture-patterns/index.md similarity index 96% rename from docs/research/microsoft-foundry/agent-memory-architecture-patterns/index.md rename to docs/services/microsoft-foundry/agent-memory/architecture-patterns/index.md index 9967a61..d942097 100644 --- a/docs/research/microsoft-foundry/agent-memory-architecture-patterns/index.md +++ b/docs/services/microsoft-foundry/agent-memory/architecture-patterns/index.md @@ -16,6 +16,9 @@ tags: - ai-agents - architecture published_at: 2026-08-24 +topic_order: 2 +redirect_from: +- research/microsoft-foundry/agent-memory-architecture-patterns/index.md --- # 02. 아키텍처 패턴 — 어떤 구조로 담을 것인가 @@ -243,7 +246,7 @@ CPU 캐시 계층(L1/L2/메인메모리)에서 착안했다. 접근 빈도·최 | **Phase 3** | + Tiered (HOT/WARM) + Graph-Augmented | 지연·비용이 실제 문제가 되고, 상품·브랜드 관계 추천이 필요해질 때. COLD는 그 다음 | | **Phase 4** | 필요 시 Full Cognitive | 라우터 도입은 저장소가 3개 이상, 오라우팅 모니터링 체계가 갖춰진 후 | -이 Phase 구분은 [06. 커머스 적용 설계 §10 로드맵](../agent-memory-commerce/index.md)과 동일한 기준이다. **티어링을 Phase 2로 당기지 않는 것**이 핵심이다 — 저장소를 나누기 전에 retention 정책과 평가 체계가 먼저 서야 한다. +이 Phase 구분은 [06. 커머스 적용 설계 §10 로드맵](../commerce/index.md)과 동일한 기준이다. **티어링을 Phase 2로 당기지 않는 것**이 핵심이다 — 저장소를 나누기 전에 retention 정책과 평가 체계가 먼저 서야 한다. **Full Cognitive를 처음부터 짓지 말 것.** 이 패턴의 최대 약점으로 지목되는 것이 "오버엔지니어링 위험"이며, 라우터 오분류는 **조용히 실패한다**(데이터가 엉뚱한 저장소에 들어가 영영 검색되지 않음). 자체 프레임워크를 운영 중인 팀이라면 라우팅 계층을 얹기 전에 로깅·평가 체계부터 갖추는 편이 안전하다. @@ -251,8 +254,8 @@ CPU 캐시 계층(L1/L2/메인메모리)에서 착안했다. 접근 빈도·최 ## 다음 문서 -- [03. 파이프라인과 검색](../agent-memory-pipeline-retrieval/index.md) — 각 패턴 내부에서 데이터가 흐르는 방식 -- [05. 프로덕션과 평가](../agent-memory-production-evaluation/index.md) — Tiered 패턴의 운영 구현 +- [03. 파이프라인과 검색](../pipeline-retrieval/index.md) — 각 패턴 내부에서 데이터가 흐르는 방식 +- [05. 프로덕션과 평가](../production-evaluation/index.md) — Tiered 패턴의 운영 구현 --- diff --git a/docs/research/microsoft-foundry/agent-memory-commerce/images/05-commerce-blueprint.svg b/docs/services/microsoft-foundry/agent-memory/commerce/images/05-commerce-blueprint.svg similarity index 100% rename from docs/research/microsoft-foundry/agent-memory-commerce/images/05-commerce-blueprint.svg rename to docs/services/microsoft-foundry/agent-memory/commerce/images/05-commerce-blueprint.svg diff --git a/docs/research/microsoft-foundry/agent-memory-commerce/index.md b/docs/services/microsoft-foundry/agent-memory/commerce/index.md similarity index 99% rename from docs/research/microsoft-foundry/agent-memory-commerce/index.md rename to docs/services/microsoft-foundry/agent-memory/commerce/index.md index 019ae80..a0eb172 100644 --- a/docs/research/microsoft-foundry/agent-memory-commerce/index.md +++ b/docs/services/microsoft-foundry/agent-memory/commerce/index.md @@ -16,6 +16,9 @@ tags: - ai-agents - architecture published_at: 2026-08-24 +topic_order: 6 +redirect_from: +- research/microsoft-foundry/agent-memory-commerce/index.md --- # 06. 커머스 적용 설계 — 실시간 추천과 개인 맞춤 구매 유도 diff --git a/docs/research/microsoft-foundry/agent-memory-frameworks/index.md b/docs/services/microsoft-foundry/agent-memory/frameworks/index.md similarity index 98% rename from docs/research/microsoft-foundry/agent-memory-frameworks/index.md rename to docs/services/microsoft-foundry/agent-memory/frameworks/index.md index b4c6040..4968cfd 100644 --- a/docs/research/microsoft-foundry/agent-memory-frameworks/index.md +++ b/docs/services/microsoft-foundry/agent-memory/frameworks/index.md @@ -16,6 +16,9 @@ tags: - ai-agents - architecture published_at: 2026-08-24 +topic_order: 4 +redirect_from: +- research/microsoft-foundry/agent-memory-frameworks/index.md --- # 04. 프레임워크 · 플랫폼 비교 — 만들 것인가 가져올 것인가 @@ -221,8 +224,8 @@ TTL / Decay 정책 ┃ ## 다음 문서 -- [05. 프로덕션과 평가](../agent-memory-production-evaluation/index.md) -- [06. 커머스 적용 설계](../agent-memory-commerce/index.md) +- [05. 프로덕션과 평가](../production-evaluation/index.md) +- [06. 커머스 적용 설계](../commerce/index.md) --- diff --git a/docs/research/microsoft-foundry/agent-memory-overview/index.md b/docs/services/microsoft-foundry/agent-memory/index.md similarity index 70% rename from docs/research/microsoft-foundry/agent-memory-overview/index.md rename to docs/services/microsoft-foundry/agent-memory/index.md index 57bc232..4d059a1 100644 --- a/docs/research/microsoft-foundry/agent-memory-overview/index.md +++ b/docs/services/microsoft-foundry/agent-memory/index.md @@ -16,6 +16,8 @@ tags: - ai-agents - architecture published_at: 2026-08-24 +redirect_from: +- research/microsoft-foundry/agent-memory-overview/index.md --- # Agent Memory 종합 리서치 @@ -37,34 +39,21 @@ published_at: 2026-08-24 --- -## 문서 구성 - -| 문서 | 내용 | 대상 | -|------|------|------| -| **[01. 메모리 분류 체계](../agent-memory-taxonomy/index.md)** | Agent Memory **8유형 통합 정의**, 유형별 **저장소 선택지**와 Azure 매핑, Retention 정책, 30기법 6패밀리 지도 | 전원 | -| **[02. 아키텍처 패턴](../agent-memory-architecture-patterns/index.md)** | Single-Store / Dual-Store / Tiered / Graph-Augmented / Full Cognitive 5패턴, 비교표, 선택 결정 트리 | 아키텍트 | -| **[03. 파이프라인과 검색](../agent-memory-pipeline-retrieval/index.md)** | 쓰기 경로(추출→중복·모순 해소→라우팅) / 읽기 경로(hybrid search→RRF→rerank→MMR→temporal), 단계별 지연 비용 | 개발자 | -| **[04. 프레임워크 비교](../agent-memory-frameworks/index.md)** | Mem0 · Zep · Graphiti · Letta(MemGPT) · Cognee · Azure AI Search, **자체 구현 vs 도입 판단 기준** | 의사결정자 | -| **[05. 프로덕션과 평가](../agent-memory-production-evaluation/index.md)** | Tiered storage, PII·GDPR, TTL·샤딩·관측성·비용, 평가 지표, LoCoMo/LongMemEval 벤치마크 | SRE / 개발자 | -| **[06. 커머스 적용 설계](../agent-memory-commerce/index.md)** | **메모리 스키마 · 지연 예산 · 개인화 강도 단계화 · 안티패턴 · Phase 0~4 로드맵 · 기술+비즈니스 지표** | 전원 (핵심) | - ---- - ## 다이어그램 | 파일 | 내용 | |------|------| -| [images/01-memory-taxonomy.svg](https://github.com/hellices/devguidesample/blob/main/samples/microsoft-foundry/memory-artifacts/agent-memory/images/01-memory-taxonomy.svg) | 6패밀리 30기법 전체 지도 | -| [images/02-architecture-patterns.svg](https://github.com/hellices/devguidesample/blob/main/samples/microsoft-foundry/memory-artifacts/agent-memory/images/02-architecture-patterns.svg) | 5가지 아키텍처 패턴 비교 | -| [images/03-memory-pipeline.svg](https://github.com/hellices/devguidesample/blob/main/samples/microsoft-foundry/memory-artifacts/agent-memory/images/03-memory-pipeline.svg) | 쓰기 경로 / 읽기 경로 분리 | -| [images/04-production-tiers.svg](https://github.com/hellices/devguidesample/blob/main/samples/microsoft-foundry/memory-artifacts/agent-memory/images/04-production-tiers.svg) | 프로덕션 계층 저장 + 가드레일 | -| [images/05-commerce-blueprint.svg](https://github.com/hellices/devguidesample/blob/main/samples/microsoft-foundry/memory-artifacts/agent-memory/images/05-commerce-blueprint.svg) | B2C 커머스 메모리 블루프린트 | +| [images/01-memory-taxonomy.svg](https://github.com/hellices/devguidesample/blob/main/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/01-memory-taxonomy.svg) | 6패밀리 30기법 전체 지도 | +| [images/02-architecture-patterns.svg](https://github.com/hellices/devguidesample/blob/main/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/02-architecture-patterns.svg) | 5가지 아키텍처 패턴 비교 | +| [images/03-memory-pipeline.svg](https://github.com/hellices/devguidesample/blob/main/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/03-memory-pipeline.svg) | 쓰기 경로 / 읽기 경로 분리 | +| [images/04-production-tiers.svg](https://github.com/hellices/devguidesample/blob/main/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/04-production-tiers.svg) | 프로덕션 계층 저장 + 가드레일 | +| [images/05-commerce-blueprint.svg](https://github.com/hellices/devguidesample/blob/main/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/05-commerce-blueprint.svg) | B2C 커머스 메모리 블루프린트 | --- -## 읽는 순서 +## 목적별 빠른 경로 -**시간이 없다면** → [06. 커머스 적용 설계](../agent-memory-commerce/index.md) 만 읽는다. 나머지 문서의 결론이 여기 수렴한다. +**시간이 없다면** → [06. 커머스 적용 설계](commerce/index.md) 만 읽는다. 나머지 문서의 결론이 여기 수렴한다. **설계를 시작한다면** ``` @@ -88,7 +77,7 @@ published_at: 2026-08-24 | **3** | HOT/WARM 티어링, Structured RAG, Product Graph, 관측성 | 규모에서의 지연·비용 통제 | | **4** | Memory Routing, Procedural, Self-Reflection, COLD 아카이브 | 선택적 고도화 | -상세는 [06. 커머스 적용 설계 §10](../agent-memory-commerce/index.md) 참조. +상세는 [06. 커머스 적용 설계 §10](commerce/index.md) 참조. --- @@ -116,6 +105,6 @@ published_at: 2026-08-24 | 11 | [A Survey on the Memory Mechanism of LLM based Agents (arXiv:2404.13501)](https://arxiv.org/abs/2404.13501) | 학술적 분류 체계 · [정리 저장소](https://github.com/nuster1128/LLM_Agent_Memory_Survey) | | 12 | [GDPR Article 17 — Right to erasure](https://gdpr-info.eu/art-17-gdpr/) | 전 계층 삭제 요구사항 | -> **자료 활용 원칙.** 문서 본문의 서술은 1차 자료 2건을 기준으로 구성했고, 그 안에서 언급되거나 개념의 출처가 되는 논문을 확인해 근거 문헌으로 분리했다. 표의 모든 링크는 실제 접속해 제목·저자·연도를 확인했다. 다만 RRF(SIGIR 2009)와 MMR(SIGIR 1998)은 ACM DL 유료 문헌이라 [03. 파이프라인과 검색](../agent-memory-pipeline-retrieval/index.md)에 서지 정보만 표기했다. +> **자료 활용 원칙.** 문서 본문의 서술은 1차 자료 2건을 기준으로 구성했고, 그 안에서 언급되거나 개념의 출처가 되는 논문을 확인해 근거 문헌으로 분리했다. 표의 모든 링크는 실제 접속해 제목·저자·연도를 확인했다. 다만 RRF(SIGIR 2009)와 MMR(SIGIR 1998)은 ACM DL 유료 문헌이라 [03. 파이프라인과 검색](pipeline-retrieval/index.md)에 서지 정보만 표기했다. 문서별 상세 참고 자료는 각 문서 하단에 있다. diff --git a/docs/research/microsoft-foundry/agent-memory-pipeline-retrieval/images/03-memory-pipeline.svg b/docs/services/microsoft-foundry/agent-memory/pipeline-retrieval/images/03-memory-pipeline.svg similarity index 100% rename from docs/research/microsoft-foundry/agent-memory-pipeline-retrieval/images/03-memory-pipeline.svg rename to docs/services/microsoft-foundry/agent-memory/pipeline-retrieval/images/03-memory-pipeline.svg diff --git a/docs/research/microsoft-foundry/agent-memory-pipeline-retrieval/index.md b/docs/services/microsoft-foundry/agent-memory/pipeline-retrieval/index.md similarity index 97% rename from docs/research/microsoft-foundry/agent-memory-pipeline-retrieval/index.md rename to docs/services/microsoft-foundry/agent-memory/pipeline-retrieval/index.md index a555503..69af8c2 100644 --- a/docs/research/microsoft-foundry/agent-memory-pipeline-retrieval/index.md +++ b/docs/services/microsoft-foundry/agent-memory/pipeline-retrieval/index.md @@ -17,6 +17,9 @@ tags: - ai-agents - architecture published_at: 2026-08-24 +topic_order: 3 +redirect_from: +- research/microsoft-foundry/agent-memory-pipeline-retrieval/index.md --- # 03. 파이프라인과 검색 — 쓰기 경로 / 읽기 경로 @@ -94,7 +97,7 @@ Ebbinghaus 망각 곡선(1885)의 exponential decay를 적용한다. S(t) = S₀ · e^(−λ · t) ``` -- λ가 클수록 빨리 잊는다. **half-life는 도메인과 기억 유형에 따라 수십 분에서 수백 일까지 벌어진다** — 단일 값을 쓰면 안 된다. 유형별 권장치는 [01. 메모리 분류 체계의 Retention 정책 표](../agent-memory-taxonomy/index.md)와 [06. 커머스 적용 설계 §6](../agent-memory-commerce/index.md) 참조 +- λ가 클수록 빨리 잊는다. **half-life는 도메인과 기억 유형에 따라 수십 분에서 수백 일까지 벌어진다** — 단일 값을 쓰면 안 된다. 유형별 권장치는 [01. 메모리 분류 체계의 Retention 정책 표](../taxonomy/index.md)와 [06. 커머스 적용 설계 §6](../commerce/index.md) 참조 - **접근 시 강화(reinforcement)**: 검색될 때마다 강도 부스트 → 자주 유용한 기억은 오래 살아남음 - **프루닝 임계값**: 미만이면 soft delete(아카이브) 또는 hard delete - **저장 압력 모니터**가 임계값을 동적으로 조정 → 용량 한계 근접 시 더 공격적으로 망각 @@ -235,8 +238,8 @@ LLM이 **잘못된 가상 답변**을 생성하면, 검색은 정답이 아니 ## 다음 문서 -- [04. 프레임워크 비교](../agent-memory-frameworks/index.md) — 직접 만들 것인가 가져다 쓸 것인가 -- [05. 프로덕션과 평가](../agent-memory-production-evaluation/index.md) — 이 파이프라인을 규모에서 운영하기 +- [04. 프레임워크 비교](../frameworks/index.md) — 직접 만들 것인가 가져다 쓸 것인가 +- [05. 프로덕션과 평가](../production-evaluation/index.md) — 이 파이프라인을 규모에서 운영하기 --- diff --git a/docs/research/microsoft-foundry/agent-memory-production-evaluation/images/04-production-tiers.svg b/docs/services/microsoft-foundry/agent-memory/production-evaluation/images/04-production-tiers.svg similarity index 100% rename from docs/research/microsoft-foundry/agent-memory-production-evaluation/images/04-production-tiers.svg rename to docs/services/microsoft-foundry/agent-memory/production-evaluation/images/04-production-tiers.svg diff --git a/docs/research/microsoft-foundry/agent-memory-production-evaluation/index.md b/docs/services/microsoft-foundry/agent-memory/production-evaluation/index.md similarity index 97% rename from docs/research/microsoft-foundry/agent-memory-production-evaluation/index.md rename to docs/services/microsoft-foundry/agent-memory/production-evaluation/index.md index 263d33d..3fbb651 100644 --- a/docs/research/microsoft-foundry/agent-memory-production-evaluation/index.md +++ b/docs/services/microsoft-foundry/agent-memory/production-evaluation/index.md @@ -16,6 +16,9 @@ tags: - ai-agents - reliability published_at: 2026-08-24 +topic_order: 5 +redirect_from: +- research/microsoft-foundry/agent-memory-production-evaluation/index.md --- # 05. 프로덕션 운영과 평가 @@ -201,7 +204,7 @@ demote → M턴 미접근 | relevance score 하락 | 세션 종료 | decay sco **1번과 2번을 건너뛰면 이후 모든 최적화가 추측이 된다.** -이 순서는 [06. 커머스 적용 설계 §10 로드맵](../agent-memory-commerce/index.md)의 Phase 0(1~4) → Phase 2(5) → Phase 3(6) → Phase 4(7)에 그대로 대응한다. +이 순서는 [06. 커머스 적용 설계 §10 로드맵](../commerce/index.md)의 Phase 0(1~4) → Phase 2(5) → Phase 3(6) → Phase 4(7)에 그대로 대응한다. --- @@ -222,7 +225,7 @@ demote → M턴 미접근 | relevance score 하락 | 세션 종료 | decay sco ## 다음 문서 -- [06. 커머스 적용 설계](../agent-memory-commerce/index.md) +- [06. 커머스 적용 설계](../commerce/index.md) --- diff --git a/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md b/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md new file mode 100644 index 0000000..25294a3 --- /dev/null +++ b/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md @@ -0,0 +1,5 @@ +# Agent Memory research artifacts + +This sample contains the editable SVG diagrams owned by the Agent Memory topic +package. They support its memory taxonomy, architecture patterns, retrieval +pipeline, production tiers, and commerce blueprint. diff --git a/docs/research/microsoft-foundry/agent-memory-taxonomy/images/01-memory-taxonomy.svg b/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/01-memory-taxonomy.svg similarity index 100% rename from docs/research/microsoft-foundry/agent-memory-taxonomy/images/01-memory-taxonomy.svg rename to docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/01-memory-taxonomy.svg diff --git a/samples/microsoft-foundry/memory-artifacts/agent-memory/images/02-architecture-patterns.svg b/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/02-architecture-patterns.svg similarity index 100% rename from samples/microsoft-foundry/memory-artifacts/agent-memory/images/02-architecture-patterns.svg rename to docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/02-architecture-patterns.svg diff --git a/samples/microsoft-foundry/memory-artifacts/agent-memory/images/03-memory-pipeline.svg b/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/03-memory-pipeline.svg similarity index 100% rename from samples/microsoft-foundry/memory-artifacts/agent-memory/images/03-memory-pipeline.svg rename to docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/03-memory-pipeline.svg diff --git a/samples/microsoft-foundry/memory-artifacts/agent-memory/images/04-production-tiers.svg b/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/04-production-tiers.svg similarity index 100% rename from samples/microsoft-foundry/memory-artifacts/agent-memory/images/04-production-tiers.svg rename to docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/04-production-tiers.svg diff --git a/samples/microsoft-foundry/memory-artifacts/agent-memory/images/05-commerce-blueprint.svg b/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/05-commerce-blueprint.svg similarity index 100% rename from samples/microsoft-foundry/memory-artifacts/agent-memory/images/05-commerce-blueprint.svg rename to docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/05-commerce-blueprint.svg diff --git a/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/sample.yml b/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/sample.yml new file mode 100644 index 0000000..61fc348 --- /dev/null +++ b/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/sample.yml @@ -0,0 +1,11 @@ +title: Agent Memory research artifacts +description: Diagrams supporting the Agent Memory research topic. +kind: artifact +used_by: +- index +- taxonomy +- architecture-patterns +- pipeline-retrieval +- frameworks +- production-evaluation +- commerce diff --git a/samples/microsoft-foundry/memory-artifacts/agent-memory/images/01-memory-taxonomy.svg b/docs/services/microsoft-foundry/agent-memory/taxonomy/images/01-memory-taxonomy.svg similarity index 100% rename from samples/microsoft-foundry/memory-artifacts/agent-memory/images/01-memory-taxonomy.svg rename to docs/services/microsoft-foundry/agent-memory/taxonomy/images/01-memory-taxonomy.svg diff --git a/docs/research/microsoft-foundry/agent-memory-taxonomy/index.md b/docs/services/microsoft-foundry/agent-memory/taxonomy/index.md similarity index 96% rename from docs/research/microsoft-foundry/agent-memory-taxonomy/index.md rename to docs/services/microsoft-foundry/agent-memory/taxonomy/index.md index dec9b91..a4167e4 100644 --- a/docs/research/microsoft-foundry/agent-memory-taxonomy/index.md +++ b/docs/services/microsoft-foundry/agent-memory/taxonomy/index.md @@ -16,6 +16,9 @@ tags: - ai-agents - architecture published_at: 2026-08-24 +topic_order: 1 +redirect_from: +- research/microsoft-foundry/agent-memory-taxonomy/index.md --- # 01. 메모리 분류 체계 — 무엇을 기억할 것인가 @@ -141,7 +144,7 @@ published_at: 2026-08-24 | Procedural / Persona | **No decay**, 버전 관리 | — | | Cold Archive | **Retention period** (규제 기준) | 3~5년 | -관련 개념은 [03. 파이프라인과 검색](../agent-memory-pipeline-retrieval/index.md)에서 Temporal Memory · Forgetting & Decay · Consolidation으로 자세히 다룬다. +관련 개념은 [03. 파이프라인과 검색](../pipeline-retrieval/index.md)에서 Temporal Memory · Forgetting & Decay · Consolidation으로 자세히 다룬다. --- @@ -168,7 +171,7 @@ published_at: 2026-08-24 ## 커머스 관점 우선순위 -실시간 추천 + 개인 맞춤 구매 유도라는 목표에 비춰 기법의 실효 순위를 매기면 다음과 같다. Phase 번호는 [06. 커머스 적용 설계 §10 로드맵](../agent-memory-commerce/index.md)과 같은 기준을 쓴다. +실시간 추천 + 개인 맞춤 구매 유도라는 목표에 비춰 기법의 실효 순위를 매기면 다음과 같다. Phase 번호는 [06. 커머스 적용 설계 §10 로드맵](../commerce/index.md)과 같은 기준을 쓴다. ### 선행 필수 (Phase 0) | 기법 | 이유 | @@ -211,8 +214,8 @@ published_at: 2026-08-24 ## 다음 문서 -- [02. 아키텍처 패턴](../agent-memory-architecture-patterns/index.md) — 이 메모리들을 어떤 구조로 담을 것인가 -- [06. 커머스 적용 설계](../agent-memory-commerce/index.md) — 실시간 추천/구매 유도로 직결되는 설계 +- [02. 아키텍처 패턴](../architecture-patterns/index.md) — 이 메모리들을 어떤 구조로 담을 것인가 +- [06. 커머스 적용 설계](../commerce/index.md) — 실시간 추천/구매 유도로 직결되는 설계 --- diff --git a/docs/guides/microsoft-foundry/codex-closed-network/index.md b/docs/services/microsoft-foundry/codex-closed-network/index.md similarity index 98% rename from docs/guides/microsoft-foundry/codex-closed-network/index.md rename to docs/services/microsoft-foundry/codex-closed-network/index.md index e777a8e..8c7863e 100644 --- a/docs/guides/microsoft-foundry/codex-closed-network/index.md +++ b/docs/services/microsoft-foundry/codex-closed-network/index.md @@ -20,6 +20,8 @@ tags: - networking - security - ai-agents +redirect_from: +- guides/microsoft-foundry/codex-closed-network/index.md --- # Codex ↔ Azure AI Foundry 연동: 폐쇄망 환경 가이드 diff --git a/docs/guides/microsoft-foundry/foundry-local-air-gapped/index.md b/docs/services/microsoft-foundry/foundry-local-air-gapped/index.md similarity index 99% rename from docs/guides/microsoft-foundry/foundry-local-air-gapped/index.md rename to docs/services/microsoft-foundry/foundry-local-air-gapped/index.md index 2a09060..778ca72 100644 --- a/docs/guides/microsoft-foundry/foundry-local-air-gapped/index.md +++ b/docs/services/microsoft-foundry/foundry-local-air-gapped/index.md @@ -20,6 +20,8 @@ tags: - deployment - security - ai-agents +redirect_from: +- guides/microsoft-foundry/foundry-local-air-gapped/index.md --- # Foundry Local (On-device SDK/CLI): 제약 사항과 에어갭(Air-gapped) 구성 가이드 diff --git a/docs/guides/microsoft-foundry/gpt-memory-layer/images/hybrid-architecture.png b/docs/services/microsoft-foundry/gpt-memory-layer/images/hybrid-architecture.png similarity index 100% rename from docs/guides/microsoft-foundry/gpt-memory-layer/images/hybrid-architecture.png rename to docs/services/microsoft-foundry/gpt-memory-layer/images/hybrid-architecture.png diff --git a/docs/guides/microsoft-foundry/gpt-memory-layer/index.md b/docs/services/microsoft-foundry/gpt-memory-layer/index.md similarity index 98% rename from docs/guides/microsoft-foundry/gpt-memory-layer/index.md rename to docs/services/microsoft-foundry/gpt-memory-layer/index.md index c87f86a..281dffc 100644 --- a/docs/guides/microsoft-foundry/gpt-memory-layer/index.md +++ b/docs/services/microsoft-foundry/gpt-memory-layer/index.md @@ -19,6 +19,8 @@ technologies: tags: - ai-agents - architecture +redirect_from: +- guides/microsoft-foundry/gpt-memory-layer/index.md --- # Azure Foundry GPT-5.x 메모리 레이어 아키텍처 가이드 @@ -522,7 +524,7 @@ Australia East, Brazil South, Canada East, East US 2, France Central, Italy Nort ![하이브리드 메모리 아키텍처](images/hybrid-architecture.png) - + ### 전환/마이그레이션 체크리스트 diff --git a/docs/services/microsoft-foundry/gpt-memory-layer/samples/architecture/README.md b/docs/services/microsoft-foundry/gpt-memory-layer/samples/architecture/README.md new file mode 100644 index 0000000..51d237f --- /dev/null +++ b/docs/services/microsoft-foundry/gpt-memory-layer/samples/architecture/README.md @@ -0,0 +1,5 @@ +# GPT memory architecture + +These editable Excalidraw and rendered PNG artifacts illustrate the hybrid +Prompt Cache, Redis session, and Foundry memory architecture owned by the GPT +memory layer guide. diff --git a/samples/microsoft-foundry/memory-artifacts/hybrid-architecture.excalidraw b/docs/services/microsoft-foundry/gpt-memory-layer/samples/architecture/hybrid-architecture.excalidraw similarity index 100% rename from samples/microsoft-foundry/memory-artifacts/hybrid-architecture.excalidraw rename to docs/services/microsoft-foundry/gpt-memory-layer/samples/architecture/hybrid-architecture.excalidraw diff --git a/samples/microsoft-foundry/memory-artifacts/hybrid-architecture.png b/docs/services/microsoft-foundry/gpt-memory-layer/samples/architecture/hybrid-architecture.png similarity index 100% rename from samples/microsoft-foundry/memory-artifacts/hybrid-architecture.png rename to docs/services/microsoft-foundry/gpt-memory-layer/samples/architecture/hybrid-architecture.png diff --git a/docs/services/microsoft-foundry/gpt-memory-layer/samples/architecture/sample.yml b/docs/services/microsoft-foundry/gpt-memory-layer/samples/architecture/sample.yml new file mode 100644 index 0000000..0eece24 --- /dev/null +++ b/docs/services/microsoft-foundry/gpt-memory-layer/samples/architecture/sample.yml @@ -0,0 +1,5 @@ +title: GPT memory architecture +description: Editable and rendered hybrid memory architecture diagrams. +kind: artifact +used_by: +- index diff --git a/mkdocs.yml b/mkdocs.yml index 6879dd3..2215795 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,6 +7,8 @@ edit_uri: edit/main/docs/ docs_dir: docs site_dir: site use_directory_urls: true +exclude_docs: | + services/**/samples/** validation: links: diff --git a/samples/azure-load-testing/source-material/images/01-test-list.png b/samples/azure-load-testing/source-material/images/01-test-list.png deleted file mode 100644 index 6b7450c..0000000 Binary files a/samples/azure-load-testing/source-material/images/01-test-list.png and /dev/null differ diff --git a/samples/azure-load-testing/source-material/images/02-run-summary.png b/samples/azure-load-testing/source-material/images/02-run-summary.png deleted file mode 100644 index 0b98263..0000000 Binary files a/samples/azure-load-testing/source-material/images/02-run-summary.png and /dev/null differ diff --git a/samples/azure-load-testing/source-material/images/03-request-statistics.png b/samples/azure-load-testing/source-material/images/03-request-statistics.png deleted file mode 100644 index ca9468c..0000000 Binary files a/samples/azure-load-testing/source-material/images/03-request-statistics.png and /dev/null differ diff --git a/samples/azure-load-testing/source-material/images/04-client-side-metrics.png b/samples/azure-load-testing/source-material/images/04-client-side-metrics.png deleted file mode 100644 index 68ceb1d..0000000 Binary files a/samples/azure-load-testing/source-material/images/04-client-side-metrics.png and /dev/null differ diff --git a/samples/azure-load-testing/source-material/images/05-server-side-metrics.png b/samples/azure-load-testing/source-material/images/05-server-side-metrics.png deleted file mode 100644 index 062b03c..0000000 Binary files a/samples/azure-load-testing/source-material/images/05-server-side-metrics.png and /dev/null differ diff --git a/samples/azure-load-testing/source-material/images/06-engine-health.png b/samples/azure-load-testing/source-material/images/06-engine-health.png deleted file mode 100644 index 5fbfa35..0000000 Binary files a/samples/azure-load-testing/source-material/images/06-engine-health.png and /dev/null differ diff --git a/samples/azure-load-testing/source-material/images/07-compare-select.png b/samples/azure-load-testing/source-material/images/07-compare-select.png deleted file mode 100644 index 1173952..0000000 Binary files a/samples/azure-load-testing/source-material/images/07-compare-select.png and /dev/null differ diff --git a/samples/azure-load-testing/source-material/images/08-compare-result.png b/samples/azure-load-testing/source-material/images/08-compare-result.png deleted file mode 100644 index a7bb92d..0000000 Binary files a/samples/azure-load-testing/source-material/images/08-compare-result.png and /dev/null differ diff --git a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/published_layout.py b/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/published_layout.py deleted file mode 100644 index fdd1d63..0000000 --- a/samples/azure-monitor/source-material/sre-agent-event-lab/scripts/tests/published_layout.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Canonical repository paths for the SRE lab's published documentation.""" - -from __future__ import annotations - -from pathlib import Path - - -REPO_ROOT = Path(__file__).parents[6] -LAB_ROOT = Path(__file__).parents[2] -DOCS_ROOT = REPO_ROOT / "docs" - -README = DOCS_ROOT / "labs" / "azure-monitor" / "sre-agent-event-lab" / "index.md" -GUIDE_PATHS = { - "01-agent-setup.md": ( - DOCS_ROOT / "labs" / "azure-monitor" / "sre-agent-event-lab-setup" / "index.md" - ), - "02-scenario-s1.md": ( - DOCS_ROOT / "labs" / "azure-monitor" / "sre-agent-scenario-http-500" / "index.md" - ), - "03-scenario-s2.md": ( - DOCS_ROOT / "labs" / "azure-monitor" / "sre-agent-scenario-latency" / "index.md" - ), - "04-scenario-s3.md": ( - DOCS_ROOT - / "labs" - / "azure-monitor" - / "sre-agent-scenario-blob-permission" - / "index.md" - ), - "05-results.md": ( - DOCS_ROOT / "labs" / "azure-monitor" / "sre-agent-results" / "index.md" - ), -} - - -class PublishedGuideDirectory: - """Provide the former directory interface over published page bundles.""" - - def __truediv__(self, name: str | Path) -> Path: - return GUIDE_PATHS[str(name)] - - def glob(self, pattern: str): - if pattern != "*.md": - return iter(()) - return iter(GUIDE_PATHS.values()) - - -GUIDES = PublishedGuideDirectory() -RESULTS_GUIDE = GUIDE_PATHS["05-results.md"] -RUNBOOK = DOCS_ROOT / "guides" / "azure-monitor" / "sre-agent-incident-runbook" / "index.md" -DYNAMIC_THRESHOLDS = ( - DOCS_ROOT / "guides" / "azure-monitor" / "sre-agent-dynamic-thresholds" / "index.md" -) -VALIDATION_RESULTS = ( - DOCS_ROOT / "research" / "azure-monitor" / "sre-agent-validation-results" / "index.md" -) -BRIEFING = ( - DOCS_ROOT / "guides" / "azure-monitor" / "azure-sre-agent-overview" / "index.md" -) -OFFICIAL_ASSETS = LAB_ROOT / "assets" / "official" diff --git a/samples/azure-openai/ptu-load-balancing/README.md b/samples/azure-openai/ptu-load-balancing/README.md deleted file mode 100644 index e7bd85e..0000000 --- a/samples/azure-openai/ptu-load-balancing/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# PTU LB Test Docs - -메인 문서는 [Adaptive PTU 부하 분산 가이드](../../../docs/guides/azure-openai/adaptive-ptu-load-balancing/index.md)입니다. diff --git a/samples/microsoft-foundry/memory-artifacts/README.md b/samples/microsoft-foundry/memory-artifacts/README.md deleted file mode 100644 index 810194b..0000000 --- a/samples/microsoft-foundry/memory-artifacts/README.md +++ /dev/null @@ -1,180 +0,0 @@ -# memory - -에이전트 메모리 관련 가이드 모음. - -## 구성 - -| 항목 | 내용 | -|------|------| -| **[agent-memory/](agent-memory/)** | **Agent Memory 종합 리서치 (v2)** — 아래 요약 참조 | -| [foundry-gpt-memory-v1.md](foundry-gpt-memory-v1.md) | 1차 버전. Azure Foundry GPT-5.x 3계층 메모리 (Prompt Cache → Redis Session → Foundry Memory Service) | - ---- - -## agent-memory/ 요약 - -### 1. 배경 및 목적 - -- 대부분의 대화형 에이전트는 **stateless** 구조로, 세션 종료 시 맥락이 소멸한다 -- 재방문 고객에게도 동일한 정보를 반복 확인하게 되어 개인화·학습·장기 일관성이 성립하지 않는다 -- 본 리서치는 에이전트를 운영 중이나 **메모리 계층이 아직 구성되지 않은 조직**이, 준비 단계에서 다음 세 가지를 판단할 수 있도록 정리한 것이다 - 1. 메모리에는 어떤 종류가 있는가 - 2. 각각을 어디에 저장할 수 있는가 - 3. 우리 규모에서는 어디서부터 시작해야 하는가 - ---- - -### 2. 산출물 구성 - -| 문서 | 다루는 범위 | -|------|------------| -| [01. 메모리 분류 체계](agent-memory/01-memory-taxonomy.md) | Agent Memory 8유형 정의, 유형별 저장소 선택지 및 Azure 매핑, Retention 정책 | -| [02. 아키텍처 패턴](agent-memory/02-architecture-patterns.md) | Single-Store / Dual-Store / Tiered / Graph-Augmented / Full Cognitive 5패턴과 선택 기준 | -| [03. 파이프라인과 검색](agent-memory/03-pipeline-and-retrieval.md) | 쓰기(비동기)·읽기(실시간) 경로 분리, 하이브리드 검색 단계별 비용 | -| [04. 프레임워크 비교](agent-memory/04-frameworks.md) | Mem0 · Zep · Graphiti · Letta · Cognee · Azure AI Search, 자체 구현 대비 도입 판단 | -| [05. 프로덕션과 평가](agent-memory/05-production-evaluation.md) | 계층 저장, PII·GDPR, 관측성·비용, 평가 지표 및 표준 벤치마크 | -| [06. 커머스 적용 설계](agent-memory/06-commerce-application.md) | **핵심 산출물** — 메모리 스키마, 지연 예산, 안티패턴, Phase 0~4 로드맵 | - ---- - -### 3. 메모리에는 어떤 종류가 있는가 - -#### 3.1 Agent Memory 8유형 - -"장기 메모리를 도입한다"는 말은 설계 단계에서 성립하지 않는다. 아래 중 무엇인지 특정해야 저장소와 수명이 정해진다. - -| 유형 | 무엇을 기억하나 | 범위 | 수명 | -|------|---------------|------|------| -| **Working** | 단일 요청을 처리하는 동안의 스크래치패드 | 요청 1건 | 요청 종료 시 소멸 | -| **Short-term** | 진행 중인 세션의 대화 맥락 | 세션 1건 | 세션 TTL | -| **Semantic** | 시점을 벗어난 **일반화된 사실** | 유저 | 고정 또는 장기 | -| **Episodic** | 시점·맥락을 포함한 **완결된 사건 기록** | 유저 × 시점 | 중기 | -| **Procedural** | 반복 워크플로, 툴 사용 절차 | 에이전트 | 버전 관리 | -| **Entity / Graph** | 엔티티와 **엔티티 간 관계** | 전역 + 유저 | 소멸 없음 | -| **Persona** | 에이전트 자신의 역할·톤 | 에이전트 | 버전 관리 | -| **Structured RAG** | 스키마가 있는 도메인 데이터의 정밀 검색 | 도메인 데이터 | 원본 수명에 종속 | - -**혼동하기 쉬운 두 쌍** - -- **Working vs Short-term** — Working은 요청 1건 안에서만 살고, Short-term은 세션 전체를 산다. 둘을 같게 다루면 토큰 예산 관리가 무너진다 -- **Semantic vs Episodic** — *"화요일에 알려줬다"* 는 사건(Episodic), *"생일은 3월 15일"* 은 증류된 사실(Semantic)이다. **Episodic으로 포착하고 Semantic으로 증류하는** 조합이 프로덕션의 기본형이다 - -#### 3.2 실무에서 가장 많이 쓰는 구분 — Semantic Memory의 하위 분류 - -개인화 품질은 대부분 이 네 가지를 구분했는지에서 갈린다. - -| 구분 | 판단 기준 | 보존 정책 | 예시 | -|------|----------|----------|------| -| **constraint** | 미충족 시 **구매·사용 자체가 불가** | Pinned (소멸 없음) | 통신사·자급제 구분, eSIM 지원 여부 / 업무 정책상 요구되는 노트북 OS 사양 / 보험 가입 가능 기간 및 대상 모델 / 기존 기기와의 모니터 포트·VESA 규격 / 가전 설치 공간 치수, 문 열림 방향 | -| **preference** | 만족도에 영향, 구매 불가 사유는 아님 | Long half-life (90~180일) | 폴더블 선호, 고용량 스토리지 선택 성향 / 노트북 휴대성 우선 / 모니터 고주사율 선호 / 비스포크 색상 패널 취향 | -| **intent** | 이번 구매 건에 한정, 시간 경과 시 무효 | Short half-life (30분~수시간) | 현재 탐색 중인 카테고리, 이번 건의 가격대, 약정 만료에 따른 기기 변경 검토 | -| **context** | 여러 카테고리에 공통 적용되는 배경 | Very long half-life (180일+) | 기존 보유 갤럭시 기기 및 생태계 자산, 사용 목적(업무용·학습용), 가구원 수·주거 형태 | - -> **constraint는 랭킹 가중치가 아니라 하드 필터로 처리한다.** 가중치로 두면 타 속성 점수가 높을 때 가입 불가 상품이나 호환되지 않는 제품이 상위에 노출된다. - ---- - -### 4. 어떻게 구성할 수 있는가 - -#### 4.1 유형별 저장소와 Azure 매핑 - -접근 패턴이 다르므로 유형마다 적합한 저장소가 다르다. - -| 유형 | 1순위 저장소 | Azure 매핑 | -|------|-------------|-----------| -| Working | 프로세스 인메모리 | (앱 프로세스 내부 — 외부 저장 불필요) | -| Short-term | Redis (TTL) | Azure Managed Redis | -| Semantic | PostgreSQL + pgvector | Azure Database for PostgreSQL | -| Episodic | 벡터 인덱스 + 시간 필터 | Azure AI Search, Cosmos DB | -| Procedural | JSON / YAML (Git 관리) | Blob Storage, Azure App Configuration | -| Entity / Graph | 그래프 DB | Cosmos DB for Apache Gremlin | -| Persona | 설정 파일 / 프롬프트 템플릿 | Azure App Configuration | -| Structured RAG | 검색 엔진 | Azure AI Search | -| *(Cold Archive)* | 오브젝트 스토리지 | Azure Blob Storage (Cool / Archive) | - -#### 4.2 최소 구성부터 시작한다 - -``` -[ 최소 구성 ] 저장소 2개로 개인화의 대부분을 커버한다 - Azure Managed Redis → Short-term (세션 버퍼, TTL) - Azure DB for PostgreSQL (pgvector) → Semantic (프로필) + Episodic (이벤트 테이블) - -[ 확장 구성 ] 필요가 실제로 발생한 뒤에 추가한다 - + Azure AI Search → Structured RAG (상품·주문 정밀 질의) - + 그래프 DB → Entity / Graph (관계 기반 추천) - + Blob Storage → Cold Archive (감사·규제 보존) -``` - -#### 4.3 아키텍처 패턴 5종 - -저장소를 어떻게 조합하느냐가 검색 품질과 확장성을 결정한다. - -| 패턴 | 저장소 구성 | 적합한 상황 | -|------|-----------|-----------| -| **Single-Store** | 벡터 DB 1개 | 프로토타입·데모. 커지면 검색 품질이 급락 | -| **Dual-Store** | 세션 버퍼 + 장기 저장소 | **대부분의 실서비스 출발점** | -| **Tiered** | HOT / WARM / COLD | 이력이 방대하고 지연 요구가 엄격할 때 | -| **Graph-Augmented** | 벡터 + 지식 그래프 | 엔티티 관계 기반 추천이 필요할 때 | -| **Full Cognitive** | 유형별 전용 저장소 + 라우터 | 대규모 어시스턴트. 오버엔지니어링 위험 최대 | - -> 패턴은 자연스럽게 중첩된다. **Dual-Store로 시작해 진화시키는 것**이 권장 경로이며, Full Cognitive를 처음부터 짓지 않는다. - -#### 4.4 보존 정책은 유형별로 분리한다 - -같은 저장소에 넣더라도 보존 정책은 반드시 나눈다. 하나로 통일하는 것이 가장 흔한 설계 실패다. - -| 유형 | 방식 | 값 | -|------|------|-----| -| Working | Request-scoped | — | -| Short-term | TTL | 30분 ~ 24시간 (세션 정의에 따름) | -| Semantic — constraint | Pinned (decay 면제) | — | -| Semantic — context | Very long half-life | 180일+ | -| Semantic — preference | Long half-life | 90~180일 | -| Semantic — intent | Short half-life | 30분 ~ 수시간 | -| Episodic | Medium half-life + 접근 시 강화 | 14~60일 | -| Entity / Graph · Procedural · Persona | 소멸 없음 | 버전 관리 | -| Cold Archive | 규제 기준 보존 기간 | 3~5년 | - ---- - -### 5. 도입 방안 - -#### 5.1 단계별 로드맵 - -| Phase | 주요 과제 | 기대 효과 | -|-------|----------|----------| -| 0 | 평가 하네스, baseline 측정, PII·동의 게이트 | 개선 효과의 측정 가능성 확보 | -| 1 | Dual-Store 구성, 비동기 추출, constraint 하드 필터 | 반복 질의 없는 응대 | -| 2 | 시간 인식 메모리, 통합·정리 배치 | 현재 의도와 장기 취향의 분리 | -| 3 | 계층 저장, Structured RAG, 제품 관계 그래프 | 규모 확장 시 지연·비용 통제 | -| 4 | 라우팅, 아카이브, 샤딩 | 선택적 고도화 | - -> **Phase 0을 선행하지 않을 경우, 이후의 모든 개선 활동에 대한 효과 검증이 불가능하다.** - -#### 5.2 자체 구현과 외부 도입의 경계 - -- **정책과 스키마는 직접 소유한다** — 메모리 유형 정의, 추출 규칙, 보존 정책, 저장소 라우팅 -- **인프라 컴포넌트는 가져온다** — 벡터 검색, 그래프 백엔드, 리랭커, 평가 하네스, 관측성 -- 관리형 메모리 서비스(Mem0 · Zep 등)의 도입 판단 기준은 [04. 프레임워크 비교](agent-memory/04-frameworks.md)에 정리되어 있다 - ---- - -### 6. 구성 시 유의사항 - -| 항목 | 내용 | -|------|------| -| **읽기·쓰기 경로 분리** | 사실 추출·중복 해소·라우팅은 응답 반환 후 비동기로. 실시간 경로에 두면 사용자 대기 시간이 늘어난다 | -| **행동 데이터도 입력이다** | 커머스에서는 조회·비교·구매·반품 이력이 대화만큼 중요하다. 처음부터 동일 스키마로 수집한다 | -| **틀린 기억의 비용** | 오기억 1회가 신뢰를 크게 깎는다. confidence를 함께 저장하고 낮으면 확인 절차를 둔다 | - ---- - -### 7. 그 외 문서에서 다루는 사항 - -- 하이브리드 검색 파이프라인과 단계별 지연 비용 -- 개인화 강도의 단계 정의 및 과잉 개인화 방지 기준 -- 추천 신뢰도 지표(거절률·옵트아웃률)를 포함한 기술·비즈니스 지표 체계 -- 커머스 메모리 설계에서 반복 관찰되는 안티패턴 -- GDPR 삭제권 대응을 위한 전 계층 삭제 체크리스트 - -> 개념 이해는 [01. 메모리 분류 체계](agent-memory/01-memory-taxonomy.md)와 [02. 아키텍처 패턴](agent-memory/02-architecture-patterns.md)을, 실제 적용 설계는 [06. 커머스 적용 설계](agent-memory/06-commerce-application.md)를 참조할 것. diff --git a/scripts/docs/content.py b/scripts/docs/content.py index fb31644..82fc02c 100644 --- a/scripts/docs/content.py +++ b/scripts/docs/content.py @@ -95,26 +95,43 @@ def load_document(path: Path | str, docs_dir: Path | str | None = None) -> Docum return Document(source, PurePosixPath(relative.as_posix()), metadata, body) +def _is_canonical_document_path(relative_path: PurePosixPath) -> bool: + parts = relative_path.parts + if ( + len(parts) not in {4, 5} + or parts[0] != "services" + or parts[-1] != "index.md" + or "samples" in parts + ): + return False + slugs = parts[1:-1] + return all(KEBAB_CASE.fullmatch(slug) for slug in slugs) + + +def iter_public_document_paths( + docs_dir: Path | str, taxonomy: Mapping[str, Any] +) -> Iterable[Path]: + """Yield canonical public page-bundle paths.""" + root = Path(docs_dir) + candidate_paths: list[Path] = [] + + services_root = root / "services" + if services_root.is_dir(): + for path in services_root.rglob("index.md"): + if _is_canonical_document_path(PurePosixPath(path.relative_to(root).as_posix())): + candidate_paths.append(path) + + for path in sorted(candidate_paths, key=lambda item: item.relative_to(root).as_posix()): + yield path + + def iter_public_documents( docs_dir: Path | str, taxonomy: Mapping[str, Any] ) -> Iterable[Document]: - """Yield page-bundle documents from configured public collections.""" + """Yield canonical page-bundle documents.""" root = Path(docs_dir) - collection_paths = sorted( - { - config["path"] - for config in taxonomy.get("collections", {}).values() - if isinstance(config, Mapping) and isinstance(config.get("path"), str) - } - ) - for collection_path in collection_paths: - collection_root = root / collection_path - if not collection_root.is_dir(): - continue - for path in sorted(collection_root.rglob("index.md")): - relative = path.relative_to(root) - if len(relative.parts) == 4: - yield load_document(path, docs_dir=root) + for path in iter_public_document_paths(root, taxonomy): + yield load_document(path, docs_dir=root) def _is_date(value: Any) -> bool: @@ -232,18 +249,17 @@ def validate_document( else None ) parts = document.relative_path.parts - if len(parts) != 4 or parts[-1] != "index.md" or not KEBAB_CASE.fullmatch(parts[2]): - errors.append("public documents must use ///index.md") + is_canonical_path = _is_canonical_document_path(document.relative_path) + if not is_canonical_path: + errors.append( + "public documents must use " + "services//[/]/index.md" + ) if not isinstance(collection, Mapping): if isinstance(document_type, str): errors.append(f"unknown document_type: {document_type}") else: - expected_folder = collection.get("path") - if parts and parts[0] != expected_folder: - errors.append( - f"folder '{parts[0]}' does not match {document_type} collection '{expected_folder}'" - ) status = metadata.get("status") if isinstance(status, str) and status not in collection.get("statuses", []): errors.append(f"invalid {document_type} status: {status}") diff --git a/scripts/docs/generate_indexes.py b/scripts/docs/generate_indexes.py index 669a2e6..36268de 100644 --- a/scripts/docs/generate_indexes.py +++ b/scripts/docs/generate_indexes.py @@ -1,8 +1,9 @@ -"""Generate reader destinations and legacy indexes from public document metadata.""" +"""Generate reader destinations and compatibility indexes from document metadata.""" from __future__ import annotations from collections import defaultdict +from dataclasses import dataclass import html from pathlib import Path, PurePosixPath import posixpath @@ -15,7 +16,14 @@ if str(REPO_ROOT) not in sys.path: sys.path.insert(0, str(REPO_ROOT)) -from scripts.docs.content import Document, iter_public_documents, load_taxonomy +from scripts.docs.content import Document, load_taxonomy +from scripts.docs.topics import ( + LEGACY_COLLECTIONS, + Topic, + TopicCatalog, + build_topic_catalog, + iter_topic_documents, +) # Static English eyebrow labels for each document type / overview page, matching @@ -46,6 +54,13 @@ _HOME_SLOTS = ("", "", "") +@dataclass(frozen=True) +class TopicMatch: + topic: Topic + matching_count: int + matching_documents: tuple[Document, ...] + + def _escape(value: Any) -> str: """Escape text for use inside raw (non-markdown) HTML.""" return html.escape(str(value), quote=True) @@ -211,6 +226,130 @@ def _doc_card( ) +def _topic_match_key(topic: Topic) -> tuple[str, str]: + return (topic.primary_service, topic.slug) + + +def _display_title(item: Document | TopicMatch) -> str: + if isinstance(item, TopicMatch): + return str(item.topic.entry.metadata.get("title", "")) + return str(item.metadata.get("title", "")) + + +def _topic_tags(topic: Topic) -> list[str]: + tags: list[str] = [] + for member in topic.members: + tags.extend(_metadata_values(member, "tags")) + return list(dict.fromkeys(tags)) + + +def _topic_card( + index_path: PurePosixPath, + match: TopicMatch, + taxonomy: Mapping[str, Any], + *, + extra_classes: str = "", +) -> str: + topic = match.topic + entry = topic.entry + metadata = entry.metadata + document_type = metadata.get("document_type", "") + link = _relative_link(index_path, entry) + title = metadata.get("title", "") + description = metadata.get("description", "") + services = taxonomy.get("services", {}) + if index_path.parts[:1] == ("services",) and len(index_path.parts) == 3: + service_label = _label_for(services, index_path.parts[1]) + else: + service_label = " · ".join( + _label_for(services, service) for service in _metadata_values(entry, "services") + ) + topic_count = len(topic.members) + if match.matching_count == topic_count: + count_label = f"{topic_count}개 문서" + else: + count_label = f"{match.matching_count} / {topic_count}개 문서 일치" + + meta_spans = "" + if service_label: + meta_spans += f"{_escape(service_label)}" + meta_spans += f"{_escape(count_label)}" + + classes = f"dg-doc-card dg-topic-card dg-doc-card--{document_type}" + if extra_classes: + classes = f"{classes} {extra_classes}" + + tag_html = build_tag_links(index_path, _topic_tags(topic)[:3], taxonomy) + member_links = "" + if len(index_path.parts) == 3 and index_path.parts[0] in LEGACY_COLLECTIONS: + member_links = "".join( + f"- [{_md_label(member.metadata.get('title', ''))}]({_relative_link(index_path, member)}){{ .dg-topic-child-link }}\n" + for member in match.matching_documents + if member.relative_path != entry.relative_path + ) + if member_links: + member_links = f"\n{member_links}" + return ( + f'
\n\n' + f'
\n{meta_spans}\n
\n\n' + f'### [{_md_label(title)}]({link})\n\n' + f'

{_escape(description)}

\n' + f"{tag_html}" + f"{member_links}" + f"\n
\n" + ) + + +def _collapse_documents( + documents: Sequence[Document], catalog: TopicCatalog | None +) -> list[Document | TopicMatch]: + if catalog is None: + return sorted(documents, key=lambda document: str(document.metadata.get("title", "")).casefold()) + + matched_paths: dict[tuple[str, str], set[PurePosixPath]] = {} + standalone: dict[PurePosixPath, Document] = {} + for document in documents: + topic = catalog.by_document.get(document.relative_path) + if topic is None: + standalone.setdefault(document.relative_path, document) + continue + key = _topic_match_key(topic) + member_paths = {member.relative_path for member in topic.members} + if len(topic.members) <= 1: + standalone.setdefault( + document.relative_path if document.relative_path not in member_paths else topic.entry.relative_path, + document if document.relative_path not in member_paths else topic.entry, + ) + continue + if document.relative_path not in member_paths: + standalone.setdefault(document.relative_path, document) + continue + if topic.entry.relative_path.parts[0] != "services": + standalone.setdefault(document.relative_path, document) + continue + matched_paths.setdefault(key, set()).add(document.relative_path) + + topic_matches = [ + TopicMatch( + topic=catalog.topics[key], + matching_count=len(paths), + matching_documents=tuple( + member for member in catalog.topics[key].members + if member.relative_path in paths + ), + ) + for key, paths in matched_paths.items() + ] + collapsed: list[Document | TopicMatch] = [*standalone.values(), *topic_matches] + return sorted(collapsed, key=lambda item: _display_title(item).casefold()) + + +def _visible_documents( + documents: Sequence[Document], catalog: TopicCatalog | None +) -> list[Document]: + return list(documents) + + def _page_heading(eyebrow_prefix: str, count: int, title: str, description: str) -> str: eyebrow = f"{eyebrow_prefix} / {count} DOCUMENTS" return ( @@ -236,12 +375,32 @@ def _empty_state(message: str) -> str: return f'

{_escape(message)}

\n' +def _primary_service(document: Document, catalog: TopicCatalog | None) -> str | None: + if catalog is not None: + topic = catalog.by_document.get(document.relative_path) + if topic is not None: + return topic.primary_service + parts = document.relative_path.parts + return parts[1] if len(parts) >= 2 else None + + def _document_grid( - index_path: PurePosixPath, documents: list[Document], taxonomy: Mapping[str, Any] + index_path: PurePosixPath, + documents: list[Document], + taxonomy: Mapping[str, Any], + *, + catalog: TopicCatalog | None = None, ) -> str: if not documents: return _empty_state("아직 등록된 문서가 없습니다.") - cards = "\n".join(_doc_card(index_path, document, taxonomy) for document in documents) + cards = "\n".join( + ( + _topic_card(index_path, item, taxonomy) + if isinstance(item, TopicMatch) + else _doc_card(index_path, item, taxonomy) + ) + for item in _collapse_documents(documents, catalog) + ) return f'
\n\n{cards}\n
\n' @@ -250,6 +409,8 @@ def _build_collection_page( config: Mapping[str, Any], matching: list[Document], taxonomy: Mapping[str, Any], + *, + catalog: TopicCatalog | None = None, ) -> tuple[PurePosixPath, str]: index_path = PurePosixPath(config["path"]) / "index.md" title = config.get("title", document_type) @@ -259,9 +420,9 @@ def _build_collection_page( by_service: dict[str, list[Document]] = defaultdict(list) for document in matching: - parts = document.relative_path.parts - if len(parts) >= 2: - by_service[parts[1]].append(document) + service = _primary_service(document, catalog) + if service: + by_service[service].append(document) ordered_service_keys = _ordered_keys(by_service.keys(), services.keys()) @@ -282,8 +443,12 @@ def _build_collection_page( label = _label_for(services, slug) body += f"## {_md_label(label)} {{ #service-{slug} }}\n\n" body += '
\n\n' - for document in by_service[slug]: - body += _doc_card(index_path, document, taxonomy) + for item in _collapse_documents(by_service[slug], catalog): + body += ( + _topic_card(index_path, item, taxonomy) + if isinstance(item, TopicMatch) + else _doc_card(index_path, item, taxonomy) + ) body += "\n" body += "
\n\n" @@ -297,6 +462,7 @@ def _build_service_page( taxonomy: Mapping[str, Any], *, page_path: PurePosixPath | None = None, + catalog: TopicCatalog | None = None, ) -> tuple[PurePosixPath, str]: if page_path is None: page_path = PurePosixPath("services") / slug / "index.md" @@ -309,7 +475,7 @@ def _build_service_page( body += _page_heading(_SERVICES_EYEBROW, len(matching), label, description) body += "\n" - body += _document_grid(page_path, matching, taxonomy) + body += _document_grid(page_path, matching, taxonomy, catalog=catalog) body += "\n" return page_path, body @@ -330,6 +496,10 @@ def _build_services_overview_page( body += "\n" if not services: + available_slugs = sorted(by_service) + else: + available_slugs = _ordered_keys(set(services) | set(by_service), services.keys()) + if not available_slugs: body += _empty_state("아직 등록된 서비스가 없습니다.") else: # Nonempty services surface first (highest document count first, then @@ -337,7 +507,7 @@ def _build_services_overview_page( # services are still rendered afterward with an explicit 0 marker to # preserve their paths. ordered_slugs = sorted( - services, + available_slugs, key=lambda slug: ( len(by_service.get(slug, [])) == 0, -len(by_service.get(slug, [])), @@ -346,7 +516,7 @@ def _build_services_overview_page( ) body += '
\n\n' for slug in ordered_slugs: - label = services[slug] + label = _label_for(services, slug) matching = by_service.get(slug, []) body += '
\n\n' body += f"## [{_md_label(label)}]({slug}/index.md)\n\n" @@ -359,7 +529,10 @@ def _build_services_overview_page( def _build_tag_pages( - documents: list[Document], taxonomy: Mapping[str, Any] + documents: list[Document], + taxonomy: Mapping[str, Any], + *, + catalog: TopicCatalog | None = None, ) -> dict[PurePosixPath, str]: by_tag: dict[str, list[Document]] = defaultdict(list) for document in documents: @@ -392,7 +565,7 @@ def _build_tag_pages( body += '
\n\n' body += _page_heading("TAG", len(matching), label, tag_description) body += '\n[모든 태그](index.md){ .dg-text-link }\n\n' - body += _document_grid(page_path, matching, taxonomy) + body += _document_grid(page_path, matching, taxonomy, catalog=catalog) body += "
\n" pages[page_path] = body overview += "
\n" @@ -402,7 +575,10 @@ def _build_tag_pages( def _build_articles_page( - documents: list[Document], taxonomy: Mapping[str, Any] + documents: list[Document], + taxonomy: Mapping[str, Any], + *, + catalog: TopicCatalog | None = None, ) -> tuple[PurePosixPath, str]: page_path = PurePosixPath("articles/index.md") title = "전체 글" @@ -411,35 +587,59 @@ def _build_articles_page( body += '
\n\n' body += _page_heading("ARTICLES", len(documents), title, description) body += '\n[서비스별 보기](../services/index.md){ .dg-text-link } · [태그별 보기](../tags/index.md){ .dg-text-link }\n\n' - body += _document_grid(page_path, documents, taxonomy) + body += _document_grid(page_path, documents, taxonomy, catalog=catalog) body += "
\n" return page_path, body def build_index_pages( - documents: Iterable[Document], taxonomy: Mapping[str, Any] + documents: Iterable[Document], + taxonomy: Mapping[str, Any], + *, + catalog: TopicCatalog | None = None, ) -> dict[PurePosixPath, str]: """Return virtual Markdown pages keyed by their docs-relative path.""" - docs = sorted(documents, key=lambda item: str(item.metadata.get("title", "")).casefold()) + docs = sorted( + _visible_documents(list(documents), catalog), + key=lambda item: ( + str(item.metadata.get("title", "")).casefold(), + item.relative_path.as_posix(), + ), + ) pages: dict[PurePosixPath, str] = {} collections = taxonomy.get("collections", {}) + compatibility_documents: dict[PurePosixPath, dict[PurePosixPath, Document]] = defaultdict(dict) for document_type, config in collections.items(): matching = [doc for doc in docs if doc.metadata.get("document_type") == document_type] - index_path, body = _build_collection_page(document_type, config, matching, taxonomy) + index_path, body = _build_collection_page( + document_type, config, matching, taxonomy, catalog=catalog + ) pages[index_path] = body - by_primary_service: dict[str, list[Document]] = defaultdict(list) for document in matching: - by_primary_service[document.relative_path.parts[1]].append(document) - for slug, service_docs in by_primary_service.items(): - # A real section index prevents Material from promoting the first bundle. - service_path, service_body = _build_service_page( - slug, - service_docs, - taxonomy, - page_path=PurePosixPath(config["path"]) / slug / "index.md", - ) - pages[service_path] = service_body + service = _primary_service(document, catalog) + if service: + service_path = PurePosixPath(config["path"]) / service / "index.md" + compatibility_documents[service_path][document.relative_path] = document + + if catalog is not None: + by_path = {document.relative_path: document for document in docs} + for redirect_path, canonical_path in sorted(catalog.redirects.items()): + document = by_path.get(canonical_path) + if document is not None: + service_path = redirect_path.parent.parent / "index.md" + compatibility_documents[service_path][canonical_path] = document + + for service_path, members in sorted(compatibility_documents.items()): + # Merge current and former section membership without duplicating documents. + service_path, service_body = _build_service_page( + service_path.parts[1], + [members[path] for path in sorted(members)], + taxonomy, + page_path=service_path, + catalog=catalog, + ) + pages[service_path] = service_body by_service: dict[str, list[Document]] = defaultdict(list) for document in docs: @@ -447,14 +647,19 @@ def build_index_pages( by_service[service].append(document) services = taxonomy.get("services", {}) - for slug in services: - page_path, page_body = _build_service_page(slug, by_service.get(slug, []), taxonomy) + for slug in _ordered_keys(set(services) | set(by_service), services.keys()): + page_path, page_body = _build_service_page( + slug, + by_service.get(slug, []), + taxonomy, + catalog=catalog, + ) pages[page_path] = page_body services_path, services_body = _build_services_overview_page(by_service, taxonomy, len(docs)) pages[services_path] = services_body - pages.update(_build_tag_pages(docs, taxonomy)) - articles_path, articles_body = _build_articles_page(docs, taxonomy) + pages.update(_build_tag_pages(docs, taxonomy, catalog=catalog)) + articles_path, articles_body = _build_articles_page(docs, taxonomy, catalog=catalog) pages[articles_path] = articles_body return pages @@ -505,8 +710,12 @@ def sort_key(document: Document) -> tuple[int, str]: return selected[:3] -def _feature_card(document: Document, taxonomy: Mapping[str, Any]) -> str: +def _feature_card( + document: Document | TopicMatch, taxonomy: Mapping[str, Any] +) -> str: index_path = PurePosixPath("index.md") + if isinstance(document, TopicMatch): + return _topic_card(index_path, document, taxonomy, extra_classes="dg-feature-card") return _doc_card(index_path, document, taxonomy, extra_classes="dg-feature-card") @@ -536,14 +745,19 @@ def _build_home_stats(documents: list[Document]) -> str: return body -def _build_home_featured(documents: list[Document], taxonomy: Mapping[str, Any]) -> str: +def _build_home_featured( + documents: list[Document], + taxonomy: Mapping[str, Any], + *, + catalog: TopicCatalog | None = None, +) -> str: selected = _select_featured(documents, taxonomy) if not selected: return _empty_state("아직 소개할 문서가 없습니다.") body = '
\n\n' - for document in selected: - body += _feature_card(document, taxonomy) + for item in _collapse_documents(selected, catalog): + body += _feature_card(item, taxonomy) body += "\n" body += "
\n" return body @@ -569,8 +783,40 @@ def _build_home_browse(documents: list[Document]) -> str: return body +def build_redirect_pages(catalog: TopicCatalog) -> dict[PurePosixPath, str]: + pages: dict[PurePosixPath, str] = {} + front_matter = yaml.safe_dump( + { + "title": "문서 이동", + "search": {"exclude": True}, + "hide": ["navigation", "toc"], + }, + allow_unicode=True, + default_flow_style=False, + sort_keys=False, + ).rstrip() + for redirect_path, canonical_path in sorted(catalog.redirects.items()): + target = posixpath.relpath( + canonical_path.parent.as_posix(), start=redirect_path.parent.as_posix() + ).rstrip("/") + "/" + label = canonical_path.as_posix() + pages[redirect_path] = ( + f"---\n{front_matter}\n---\n\n" + f'\n' + f'\n\n' + "# 문서 이동\n\n" + "이 문서는 새 위치로 이동했습니다.\n\n" + f'

{_escape(label)}

\n' + ) + return pages + + def build_home_page( - template: str, documents: Iterable[Document], taxonomy: Mapping[str, Any] + template: str, + documents: Iterable[Document], + taxonomy: Mapping[str, Any], + *, + catalog: TopicCatalog | None = None, ) -> str: """Render the home landing page by filling metadata-driven slots in ``template``.""" for slot in _HOME_SLOTS: @@ -580,11 +826,18 @@ def build_home_page( if occurrences > 1: raise ValueError(f"home template has duplicate slot: {slot}") - docs = sorted(documents, key=lambda item: str(item.metadata.get("title", "")).casefold()) + docs = sorted( + _visible_documents(list(documents), catalog), + key=lambda item: str(item.metadata.get("title", "")).casefold(), + ) rendered = template rendered = rendered.replace("", _build_home_stats(docs), 1) - rendered = rendered.replace("", _build_home_featured(docs, taxonomy), 1) + rendered = rendered.replace( + "", + _build_home_featured(docs, taxonomy, catalog=catalog), + 1, + ) rendered = rendered.replace( "", _build_home_browse(docs), 1 ) @@ -597,14 +850,19 @@ def write_generated_pages(repo_root: Path | None = None) -> None: root = repo_root or REPO_ROOT taxonomy = load_taxonomy(root / "docs-taxonomy.yml") - documents = list(iter_public_documents(root / "docs", taxonomy)) + public_documents = list(iter_topic_documents(root / "docs", taxonomy)) + catalog = build_topic_catalog(root / "docs", taxonomy, documents=public_documents) + documents = list(catalog.documents) - for path, content in build_index_pages(documents, taxonomy).items(): + for path, content in build_index_pages(documents, taxonomy, catalog=catalog).items(): + with mkdocs_gen_files.open(path.as_posix(), "w") as generated: + generated.write(content) + for path, content in build_redirect_pages(catalog).items(): with mkdocs_gen_files.open(path.as_posix(), "w") as generated: generated.write(content) template = (root / "docs" / "index.md").read_text(encoding="utf-8") - home_page = build_home_page(template, documents, taxonomy) + home_page = build_home_page(template, documents, taxonomy, catalog=catalog) with mkdocs_gen_files.open("index.md", "w") as generated: generated.write(home_page) diff --git a/scripts/docs/hooks.py b/scripts/docs/hooks.py index a27da28..5bd0ba5 100644 --- a/scripts/docs/hooks.py +++ b/scripts/docs/hooks.py @@ -5,9 +5,12 @@ from collections import defaultdict from html import escape from pathlib import Path, PurePosixPath +import posixpath +import re import sys from typing import Any, Mapping +from mkdocs.structure.files import InclusionLevel from mkdocs.structure import StructureItem from mkdocs.structure.nav import Navigation, Section from mkdocs.structure.pages import Page @@ -17,8 +20,9 @@ if str(REPO_ROOT) not in sys.path: sys.path.insert(0, str(REPO_ROOT)) -from scripts.docs.content import iter_public_documents, load_taxonomy +from scripts.docs.content import load_taxonomy from scripts.docs.generate_indexes import build_tag_links +from scripts.docs.topics import TopicCatalog, build_topic_catalog, iter_topic_documents def _navigation_pages( @@ -34,22 +38,209 @@ def _navigation_pages( return pages +def on_files(files: Any, config: Mapping[str, Any]) -> Any: + """Keep internal sample READMEs out of the published site and search index.""" + try: + config.pop("_topic_catalog", None) # type: ignore[union-attr] + except Exception: + pass + for file in files: + src_uri = getattr(file, "src_uri", "") + if isinstance(src_uri, str) and "/samples/" in src_uri: + file.inclusion = InclusionLevel.EXCLUDED + return files + + +def _topic_catalog_for(docs_dir: str) -> TopicCatalog: + docs_path = Path(docs_dir) + taxonomy = load_taxonomy(docs_path.parent / "docs-taxonomy.yml") + public_documents = list(iter_topic_documents(docs_path, taxonomy)) + return build_topic_catalog(docs_path, taxonomy, documents=public_documents) + + +def _topic_catalog_from_config(config: Mapping[str, Any]) -> TopicCatalog | None: + cached = config.get("_topic_catalog") if hasattr(config, "get") else None + if isinstance(cached, TopicCatalog): + return cached + + docs_dir_value = config.get("docs_dir") if hasattr(config, "get") else None + if not isinstance(docs_dir_value, str): + return None + + catalog = _topic_catalog_for(docs_dir_value) + try: + config["_topic_catalog"] = catalog # type: ignore[index] + except Exception: + pass + return catalog + + +def _strip_index_markdown(path: PurePosixPath) -> PurePosixPath: + if path.name == "index.md": + return path.parent + return path + + +def _relative_uri(source: PurePosixPath, target: PurePosixPath) -> str: + is_index = target.name == "index.md" + target_value = target.parent.as_posix() if is_index else target.as_posix() + relative = posixpath.relpath(target_value, start=source.parent.as_posix()) + if is_index: + return "./" if relative == "." else relative.rstrip("/") + "/" + return relative + + +def _redirect_target(redirect_path: PurePosixPath, canonical_path: PurePosixPath) -> str: + target = _strip_index_markdown(canonical_path) + relative = posixpath.relpath(target.as_posix(), start=redirect_path.parent.as_posix()) + return "./" if relative == "." else relative.rstrip("/") + "/" + + +def _service_label(taxonomy: Mapping[str, Any], slug: str) -> str: + services = taxonomy.get("services", {}) + value = services.get(slug) if isinstance(services, Mapping) else None + return value if isinstance(value, str) and value else slug + + +def _sample_cards(document_path: PurePosixPath, catalog: TopicCatalog, config: Mapping[str, Any]) -> str: + samples = catalog.samples_by_document.get(document_path, ()) + if not samples: + return "" + + repo_url = str(config.get("repo_url", "")).rstrip("/") + if not repo_url: + return "" + docs_dir_value = str(config.get("docs_dir", "")).strip() + docs_prefix = Path(docs_dir_value).name.strip("/") if docs_dir_value else "" + cards: list[str] = [ + '") + return "\n".join(cards) + "\n" + + +def _topic_intro(current_path: PurePosixPath, catalog: TopicCatalog) -> str: + topic = catalog.by_document.get(current_path) + if topic is None or topic.entry.relative_path.parts[0] != "services": + return "" + member_paths = [member.relative_path for member in topic.members] + if current_path not in member_paths or len(member_paths) <= 1: + return "" + + position = catalog.position_by_document[current_path] + topic_title = escape(str(topic.entry.metadata.get("title", ""))) + pieces: list[str] = [] + + if position == 0: + pieces.append('") + else: + pieces.append('") + + return "\n".join(pieces) + "\n" + + +def _topic_nav(current_path: PurePosixPath, catalog: TopicCatalog) -> str: + topic = catalog.by_document.get(current_path) + if topic is None or topic.entry.relative_path.parts[0] != "services": + return "" + member_paths = [member.relative_path for member in topic.members] + if current_path not in member_paths or len(member_paths) <= 1: + return "" + + position = catalog.position_by_document[current_path] + topic_title = escape(str(topic.entry.metadata.get("title", ""))) + pieces: list[str] = ['") + + return "\n".join(pieces) + "\n" + + def on_nav(nav: Navigation, config: Mapping[str, Any], files: Any) -> Navigation: - """Keep each article under its primary service without changing its URL.""" + """Place canonical topic packages under their primary service.""" docs_dir = Path(config["docs_dir"]) taxonomy = load_taxonomy(docs_dir.parent / "docs-taxonomy.yml") - document_titles = { - document.relative_path.as_posix(): str(document.metadata.get("title", "")).casefold() - for document in iter_public_documents(docs_dir, taxonomy) - } + catalog = _topic_catalog_from_config(config) + if catalog is None: + return nav collections = {entry["path"] for entry in taxonomy["collections"].values()} collection_indexes = {f"{collection}/index.md" for collection in collections} pages_by_path = {page.file.src_uri: page for page in nav.pages} - by_service: dict[str, list[Page]] = defaultdict(list) + by_service: dict[str, list[tuple[str, StructureItem]]] = defaultdict(list) + for topic in catalog.topics.values(): + entry_parts = topic.entry.relative_path.parts + if entry_parts[0] == "services": + member_pages: list[Page] = [] + for position, member in enumerate(topic.members): + page = pages_by_path.get(member.relative_path.as_posix()) + if page is None: + continue + if position: + page.title = f"{position}. {member.metadata.get('title', '')}" + else: + page.title = str(member.metadata.get("title", page.title or "")) + member_pages.append(page) + if not member_pages: + continue + if len(member_pages) == 1: + by_service[topic.primary_service].append( + (str(topic.entry.metadata.get("title", "")).casefold(), member_pages[0]) + ) + else: + by_service[topic.primary_service].append( + ( + str(topic.entry.metadata.get("title", "")).casefold(), + Section(str(topic.entry.metadata.get("title", "")), member_pages), + ) + ) + continue + for page in nav.pages: - parts = PurePosixPath(page.file.src_uri).parts - if len(parts) == 4 and parts[0] in collections and parts[-1] == "index.md": - by_service[parts[1]].append(page) page.parent = None page.previous_page = None page.next_page = None @@ -64,17 +255,18 @@ def on_nav(nav: Navigation, config: Mapping[str, Any], files: Any) -> Navigation page.file.src_uri == "services/index.md" for page in direct_pages ): service_items: list[StructureItem] = [pages_by_path["services/index.md"]] - services = taxonomy["services"] - for slug in sorted(services, key=lambda value: str(services[value]).casefold()): + services = taxonomy.get("services", {}) + ordered_slugs = sorted( + set(services) | set(by_service), + key=lambda value: _service_label(taxonomy, value).casefold(), + ) + for slug in ordered_slugs: page = pages_by_path[f"services/{slug}/index.md"] - documents = sorted( - by_service.get(slug, []), - key=lambda entry: document_titles[entry.file.src_uri], - ) + documents = [entry for _, entry in sorted(by_service.get(slug, []), key=lambda value: value[0])] if not documents: service_items.append(page) else: - service_items.append(Section(services[slug], [page, *documents])) + service_items.append(Section(_service_label(taxonomy, slug), [page, *documents])) item.children = service_items items.append(item) @@ -82,6 +274,17 @@ def on_nav(nav: Navigation, config: Mapping[str, Any], files: Any) -> Navigation for index, page in enumerate(pages): page.previous_page = pages[index - 1] if index else None page.next_page = pages[index + 1] if index + 1 < len(pages) else None + for topic in catalog.topics.values(): + if topic.entry.relative_path.parts[0] != "services": + continue + member_pages = [ + pages_by_path[member.relative_path.as_posix()] + for member in topic.members + if member.relative_path.as_posix() in pages_by_path + ] + for index, page in enumerate(member_pages): + page.previous_page = member_pages[index - 1] if index else None + page.next_page = member_pages[index + 1] if index + 1 < len(member_pages) else None return Navigation(items, pages) @@ -100,6 +303,17 @@ def on_page_markdown(markdown: str, page: Any, config: Mapping[str, Any], files: if not metadata.get("document_type"): return markdown + taxonomy: Mapping[str, Any] = {} + catalog: TopicCatalog | None = None + current_path: PurePosixPath | None = None + docs_dir_value = config.get("docs_dir") + page_file = getattr(page, "file", None) + src_uri = getattr(page_file, "src_uri", None) + if isinstance(docs_dir_value, str) and isinstance(src_uri, str): + docs_dir = Path(docs_dir_value) + taxonomy = load_taxonomy(docs_dir.parent / "docs-taxonomy.yml") + catalog = _topic_catalog_from_config(config) + current_path = PurePosixPath(src_uri) sources = metadata.get("official_sources") or [] source_links = "\n".join( f'
  • ' @@ -112,15 +326,25 @@ def on_page_markdown(markdown: str, page: Any, config: Mapping[str, Any], files: if description: intro = f'

    {escape(str(description))}

    \n\n' tags = metadata.get("tags") - if isinstance(tags, list) and tags: - taxonomy = load_taxonomy(Path(config["docs_dir"]).parent / "docs-taxonomy.yml") + if isinstance(tags, list) and tags and current_path is not None: intro += build_tag_links( - PurePosixPath(page.file.src_uri), + current_path, [tag for tag in tags if isinstance(tag, str)], taxonomy, ) + if current_path is not None and catalog is not None: + topic_context = _topic_intro(current_path, catalog) + if topic_context: + intro += topic_context if intro: markdown = _insert_after_title(markdown, intro) + if current_path is not None and catalog is not None: + topic_nav = _topic_nav(current_path, catalog) + if topic_nav: + markdown = markdown.rstrip() + "\n\n" + topic_nav + sample_cards = _sample_cards(current_path, catalog, config) + if sample_cards: + markdown += "\n" + sample_cards if source_links: markdown = ( markdown.rstrip() @@ -130,3 +354,45 @@ def on_page_markdown(markdown: str, page: Any, config: Mapping[str, Any], files: + "\n\n
  • \n" ) return markdown + + +def on_post_page(output: str, page: Any, config: Mapping[str, Any]) -> str: + """Move generated redirect metadata into the final HTML head.""" + page_file = getattr(page, "file", None) + src_uri = getattr(page_file, "src_uri", None) + if not isinstance(src_uri, str): + return output + + catalog = _topic_catalog_from_config(config) + if catalog is None: + return output + + redirect_path = PurePosixPath(src_uri) + canonical_path = catalog.redirects.get(redirect_path) + if canonical_path is None: + return output + + target = escape(_redirect_target(redirect_path, canonical_path), quote=True) + output = re.sub( + r'', + f'', + output, + count=1, + ) + output = re.sub( + r"

    \s*]*>\s*]*>\s*

    ", + "", + output, + count=1, + flags=re.MULTILINE, + ) + head_match = re.search(r"(.*?)", output, flags=re.DOTALL) + head_html = head_match.group(1) if head_match else "" + additions: list[str] = [] + if 'rel="canonical"' not in head_html: + additions.append(f'') + if f'content="0; url={target}"' not in head_html: + additions.append(f'') + if additions: + output = output.replace("", "".join(item + "\n" for item in additions) + "", 1) + return output diff --git a/scripts/docs/topics.py b/scripts/docs/topics.py new file mode 100644 index 0000000..86a8998 --- /dev/null +++ b/scripts/docs/topics.py @@ -0,0 +1,368 @@ +"""Topic-package catalog for canonical documentation layouts.""" + +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path, PurePosixPath +from typing import Any, Iterable, Mapping + +import yaml + +from scripts.docs.content import Document, DocumentFormatError, KEBAB_CASE, load_document + + +LEGACY_COLLECTIONS = frozenset(("cases", "guides", "labs", "research")) + + +@dataclass(frozen=True) +class SampleAsset: + slug: str + title: str + description: str + kind: str + relative_path: PurePosixPath + used_by: tuple[PurePosixPath, ...] + + +@dataclass(frozen=True) +class Topic: + primary_service: str + slug: str + entry: Document + members: tuple[Document, ...] + samples: tuple[SampleAsset, ...] + + +@dataclass(frozen=True) +class TopicCatalog: + documents: tuple[Document, ...] + topics: dict[tuple[str, str], Topic] + by_document: dict[PurePosixPath, Topic] + position_by_document: dict[PurePosixPath, int] + samples_by_document: dict[PurePosixPath, tuple[SampleAsset, ...]] + redirects: dict[PurePosixPath, PurePosixPath] + + +def _is_canonical_document_path(relative_path: PurePosixPath) -> bool: + parts = relative_path.parts + return ( + len(parts) in {4, 5} + and parts[0] == "services" + and parts[-1] == "index.md" + and "samples" not in parts + and all(KEBAB_CASE.fullmatch(part) for part in parts[1:-1]) + ) + + +def iter_topic_documents( + docs_dir: Path | str, taxonomy: Mapping[str, Any] +) -> Iterable[Document]: + root = Path(docs_dir) + candidates: list[Path] = [] + + services_root = root / "services" + if services_root.is_dir(): + for path in services_root.rglob("index.md"): + relative = PurePosixPath(path.relative_to(root).as_posix()) + if _is_canonical_document_path(relative): + candidates.append(path) + + for path in sorted(candidates, key=lambda item: item.relative_to(root).as_posix()): + yield load_document(path, docs_dir=root) + + +def _topic_key(relative_path: PurePosixPath) -> tuple[str, str]: + return (relative_path.parts[1], relative_path.parts[2]) + + +def _sample_directory_errors( + docs_root: Path, document_paths: set[PurePosixPath] +) -> list[str]: + services_root = docs_root / "services" + if not services_root.is_dir(): + return [] + + errors: list[str] = [] + for samples_root in sorted(services_root.rglob("samples")): + if not samples_root.is_dir(): + continue + relative = PurePosixPath(samples_root.relative_to(docs_root).as_posix()) + # Only the first samples boundary defines ownership; later ones are payload. + if "samples" in relative.parts[:-1]: + continue + if len(relative.parts) != 4: + errors.append(f"{relative.as_posix()}: samples must be directly below the topic") + continue + entry_path = relative.parent / "index.md" + if entry_path not in document_paths: + errors.append(f"{entry_path.as_posix()}: topic entry document is missing") + for sample_dir in sorted(samples_root.iterdir()): + sample_path = PurePosixPath(sample_dir.relative_to(docs_root).as_posix()) + if not sample_dir.is_dir(): + errors.append( + f"{sample_path.as_posix()}: sample files must belong to a sample package" + ) + continue + if not (sample_dir / "sample.yml").is_file(): + errors.append(f"{sample_path.as_posix()}: sample.yml is required") + if not (sample_dir / "README.md").is_file(): + errors.append(f"{sample_path.as_posix()}: README.md is required") + return errors + + +def _normalize_redirects(document: Document) -> tuple[list[PurePosixPath], list[str]]: + value = document.metadata.get("redirect_from") + if value is None: + return [], [] + + errors: list[str] = [] + if not isinstance(value, list) or not value: + return [], [f"{document.relative_path.as_posix()}: redirect_from must be a non-empty list"] + + paths: list[PurePosixPath] = [] + for raw in value: + if not isinstance(raw, str) or not raw.strip(): + errors.append( + f"{document.relative_path.as_posix()}: redirect_from entries must be non-empty strings" + ) + continue + path = PurePosixPath(raw.strip()) + if ( + path.is_absolute() + or ".." in path.parts + or len(path.parts) != 4 + or path.parts[-1] != "index.md" + or path.parts[0] not in LEGACY_COLLECTIONS + ): + errors.append( + f"{document.relative_path.as_posix()}: redirect_from must use ///index.md" + ) + continue + paths.append(path) + return paths, errors + + +def _build_sample_asset( + sample_dir: Path, + docs_root: Path, + slug_to_document: Mapping[str, PurePosixPath], +) -> tuple[SampleAsset | None, list[str]]: + relative = PurePosixPath(sample_dir.relative_to(docs_root).as_posix()) + manifest_path = sample_dir / "sample.yml" + if not manifest_path.is_file() or not (sample_dir / "README.md").is_file(): + return None, [] + + try: + manifest = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) + except yaml.YAMLError as error: + return None, [f"{relative.as_posix()}: invalid sample.yml: {error}"] + if not isinstance(manifest, Mapping): + return None, [f"{relative.as_posix()}: sample.yml must be a mapping"] + + errors: list[str] = [] + values: dict[str, str] = {} + for field in ("title", "description", "kind"): + value = manifest.get(field) + if not isinstance(value, str) or not value.strip(): + errors.append(f"{relative.as_posix()}: {field} must be a non-empty string") + else: + values[field] = value.strip() + + kind = values.get("kind") + if kind is not None and kind not in {"runnable", "artifact"}: + errors.append(f"{relative.as_posix()}: kind must be runnable or artifact") + + used_by = manifest.get("used_by") + if ( + not isinstance(used_by, list) + or not used_by + or not all(isinstance(item, str) and item.strip() for item in used_by) + ): + errors.append(f"{relative.as_posix()}: used_by must be a non-empty list") + return None, errors + + if len({item.strip() for item in used_by}) != len(used_by): + errors.append(f"{relative.as_posix()}: used_by values must be unique") + + linked_documents: list[PurePosixPath] = [] + for item in used_by: + document_path = slug_to_document.get(item.strip()) + if document_path is None: + errors.append(f"{relative.as_posix()}: unknown document slug: {item}") + else: + linked_documents.append(document_path) + + if errors: + return None, errors + + return ( + SampleAsset( + slug=sample_dir.name, + title=values["title"], + description=values["description"], + kind=values["kind"], + relative_path=relative, + used_by=tuple(linked_documents), + ), + [], + ) + + +def build_topic_catalog( + docs_dir: Path | str, + taxonomy: Mapping[str, Any], + documents: Iterable[Document] | None = None, +) -> TopicCatalog: + root = Path(docs_dir) + if documents is None: + documents = iter_topic_documents(root, taxonomy) + ordered_documents = tuple( + sorted( + ( + document + for document in documents + if _is_canonical_document_path(document.relative_path) + ), + key=lambda document: document.relative_path.as_posix(), + ) + ) + canonical_paths = {document.relative_path for document in ordered_documents} + errors = _sample_directory_errors(root, canonical_paths) + + services_root = root / "services" + if services_root.is_dir(): + for path in sorted(services_root.rglob("index.md")): + relative = PurePosixPath(path.relative_to(root).as_posix()) + if "samples" in relative.parts: + continue + if len(relative.parts) > 5: + errors.append( + f"{relative.as_posix()}: child documents must be directly below the topic" + ) + + grouped: dict[tuple[str, str], list[Document]] = {} + for document in ordered_documents: + grouped.setdefault(_topic_key(document.relative_path), []).append(document) + + topics: dict[tuple[str, str], Topic] = {} + by_document: dict[PurePosixPath, Topic] = {} + position_by_document: dict[PurePosixPath, int] = {} + samples_by_document: dict[PurePosixPath, tuple[SampleAsset, ...]] = {} + + for key in sorted(grouped): + service, slug = key + members = grouped[key] + member_by_path = {member.relative_path: member for member in members} + entry_path = PurePosixPath("services", service, slug, "index.md") + entry = member_by_path.get(entry_path) + if entry is None: + errors.append(f"{entry_path.as_posix()}: topic entry document is missing") + continue + + children: list[tuple[int, Document]] = [] + orders: dict[int, list[Document]] = {} + for member in members: + if member.relative_path == entry_path: + continue + if member.relative_path.parts[3] == "index": + errors.append( + f"{member.relative_path.as_posix()}: child slug 'index' is reserved for the topic entry" + ) + continue + order = member.metadata.get("topic_order") + if isinstance(order, bool) or not isinstance(order, int) or order <= 0: + errors.append( + f"{member.relative_path.as_posix()}: topic_order must be a positive integer" + ) + continue + orders.setdefault(order, []).append(member) + children.append((order, member)) + + for order, duplicates in sorted(orders.items()): + if len(duplicates) > 1: + joined = ", ".join( + item.relative_path.as_posix() + for item in sorted(duplicates, key=lambda document: document.relative_path.as_posix()) + ) + errors.append( + f"{duplicates[0].relative_path.as_posix()}: duplicate topic_order {order}: {joined}" + ) + + ordered_children = [member for _, member in sorted(children, key=lambda item: item[0])] + actual_orders = [order for order, _ in sorted(children, key=lambda item: item[0])] + if actual_orders != list(range(1, len(ordered_children) + 1)): + errors.append( + f"{entry.relative_path.as_posix()}: topic_order values must be contiguous from 1" + ) + + topic_root = root / "services" / service / slug + member_paths = [entry.relative_path, *[child.relative_path for child in ordered_children]] + slug_to_document = { + "index": entry.relative_path, + **{ + child.relative_path.parts[3]: child.relative_path + for child in ordered_children + }, + } + samples: list[SampleAsset] = [] + samples_root = topic_root / "samples" + if samples_root.is_dir(): + for sample_dir in sorted(path for path in samples_root.iterdir() if path.is_dir()): + sample, sample_errors = _build_sample_asset(sample_dir, root, slug_to_document) + errors.extend(sample_errors) + if sample is not None: + samples.append(sample) + + if errors: + continue + + ordered_samples = tuple(sorted(samples, key=lambda item: item.title.casefold())) + members_tuple = (entry, *ordered_children) + topic = Topic( + primary_service=service, + slug=slug, + entry=entry, + members=members_tuple, + samples=ordered_samples, + ) + topics[key] = topic + for position, member in enumerate(members_tuple): + by_document[member.relative_path] = topic + position_by_document[member.relative_path] = position + samples_by_document[member.relative_path] = tuple( + sample for sample in ordered_samples if member.relative_path in sample.used_by + ) + + redirects: dict[PurePosixPath, PurePosixPath] = {} + for document in ordered_documents: + redirect_paths, redirect_errors = _normalize_redirects(document) + errors.extend(redirect_errors) + for redirect_path in redirect_paths: + if redirect_path in canonical_paths: + errors.append( + f"{document.relative_path.as_posix()}: redirect_from collides with a canonical document" + ) + continue + if redirect_path in redirects: + errors.append( + f"{document.relative_path.as_posix()}: redirect_from path is already used" + ) + continue + redirects[redirect_path] = document.relative_path + + if errors: + raise DocumentFormatError("invalid topic catalog:\n" + "\n".join(errors)) + + for redirect_path, canonical_path in redirects.items(): + topic = by_document.get(canonical_path) + if topic is not None: + by_document[redirect_path] = topic + + return TopicCatalog( + documents=ordered_documents, + topics=topics, + by_document=by_document, + position_by_document=position_by_document, + samples_by_document=samples_by_document, + redirects=redirects, + ) diff --git a/scripts/docs/validate_links.py b/scripts/docs/validate_links.py index cc5209f..7ff5631 100644 --- a/scripts/docs/validate_links.py +++ b/scripts/docs/validate_links.py @@ -7,7 +7,6 @@ from html.parser import HTMLParser from pathlib import Path import sys -from typing import Any, Iterable, Mapping from urllib.parse import unquote, urlparse from markdown import Markdown @@ -17,7 +16,13 @@ if str(REPO_ROOT) not in sys.path: sys.path.insert(0, str(REPO_ROOT)) -from scripts.docs.content import DocumentFormatError, ValidationResult, load_document, load_taxonomy +from scripts.docs.content import ( + DocumentFormatError, + ValidationResult, + iter_public_document_paths, + load_taxonomy, +) +from scripts.docs.topics import iter_topic_documents @dataclass(frozen=True) @@ -45,13 +50,6 @@ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None self.links.append(_RenderedLink(target, alt_text)) -def _candidate_paths(docs_dir: Path, taxonomy: Mapping[str, Any]) -> Iterable[Path]: - for config in taxonomy.get("collections", {}).values(): - collection = docs_dir / config["path"] - if collection.is_dir(): - yield from sorted(collection.rglob("index.md")) - - def _resolve_target(page: Path, docs_dir: Path, raw_target: str) -> Path | None: target = raw_target.removeprefix("<").removesuffix(">") if target.startswith("#"): @@ -83,14 +81,15 @@ def validate_repository(repo_root: Path | str) -> ValidationResult: ) errors: list[str] = [] count = 0 - for page in _candidate_paths(docs_dir, taxonomy): + try: + documents = list(iter_topic_documents(docs_dir, taxonomy)) + except DocumentFormatError as error: + count = sum(1 for _ in iter_public_document_paths(docs_dir, taxonomy)) + return ValidationResult(count, [str(error)]) + for document in documents: count += 1 + page = document.path relative = page.relative_to(root).as_posix() - try: - document = load_document(page, docs_dir=docs_dir) - except DocumentFormatError as error: - errors.append(str(error)) - continue parser = _LinkParser() parser.feed(renderer.reset().convert(document.body)) parser.close() diff --git a/scripts/docs/validate_metadata.py b/scripts/docs/validate_metadata.py index a8fea7e..b3ce806 100644 --- a/scripts/docs/validate_metadata.py +++ b/scripts/docs/validate_metadata.py @@ -19,9 +19,12 @@ load_taxonomy, validate_document, ) +from scripts.docs.topics import build_topic_catalog, iter_topic_documents -def _candidate_paths(docs_dir: Path, taxonomy: Mapping[str, Any]) -> Iterable[Path]: +def _authoring_markdown_paths( + docs_dir: Path, taxonomy: Mapping[str, Any] +) -> Iterable[Path]: paths = { config["path"] for config in taxonomy.get("collections", {}).values() @@ -31,6 +34,19 @@ def _candidate_paths(docs_dir: Path, taxonomy: Mapping[str, Any]) -> Iterable[Pa root = docs_dir / collection if root.is_dir(): yield from sorted(root.rglob("*.md")) + services_root = docs_dir / "services" + if services_root.is_dir(): + for path in sorted(services_root.rglob("*.md")): + relative = path.relative_to(docs_dir).parts + if "samples" not in relative: + yield path + + +def _topic_catalog_errors(error: DocumentFormatError) -> list[str]: + lines = [line for line in str(error).splitlines() if line.strip()] + if lines and lines[0] == "invalid topic catalog:": + lines = lines[1:] + return [f"docs/{line}" for line in lines] def validate_repository(repo_root: Path | str, today: date | None = None) -> ValidationResult: @@ -38,19 +54,36 @@ def validate_repository(repo_root: Path | str, today: date | None = None) -> Val docs_dir = root / "docs" taxonomy = load_taxonomy(root / "docs-taxonomy.yml") errors: list[str] = [] + authoring_paths = list(_authoring_markdown_paths(docs_dir, taxonomy)) + try: + canonical_documents = list(iter_topic_documents(docs_dir, taxonomy)) + except DocumentFormatError: + canonical_documents = [] + canonical_by_path = { + document.path.resolve(): document for document in canonical_documents + } + documents = [] count = 0 - for path in _candidate_paths(docs_dir, taxonomy): + for path in authoring_paths: relative = path.relative_to(root).as_posix() count += 1 - try: - document = load_document(path, docs_dir=docs_dir) - except DocumentFormatError as error: - errors.append(str(error)) - continue + document = canonical_by_path.get(path.resolve()) + if document is None: + try: + document = load_document(path, docs_dir=docs_dir) + except DocumentFormatError as error: + errors.append(str(error)) + continue + if document.relative_path.parts[0] == "services": + documents.append(document) errors.extend( f"{relative}: {message}" for message in validate_document(document, taxonomy, today=today) ) + try: + build_topic_catalog(docs_dir, taxonomy, documents=documents) + except DocumentFormatError as error: + errors.extend(_topic_catalog_errors(error)) return ValidationResult(count, errors) diff --git a/scripts/docs/validate_public_safety.py b/scripts/docs/validate_public_safety.py index 541c974..c11a52f 100644 --- a/scripts/docs/validate_public_safety.py +++ b/scripts/docs/validate_public_safety.py @@ -32,7 +32,8 @@ } TEXT_FILENAMES = {".env", "dockerfile"} TEXT_COMPOUND_SUFFIXES = (".env.example",) -SCAN_ROOTS = ("docs", "samples") +SCAN_ROOTS = ("docs",) +REQUIRED_SCAN_ROOTS = ("docs",) UUID = r"[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}" KNOWN_ENVIRONMENT_MARKERS = ( "rg-rubicon", @@ -116,9 +117,17 @@ def validate_repository(repo_root: Path | str) -> PublicSafetyResult: root = Path(repo_root) errors = [ f"{name}: expected public content directory is missing under {root}" - for name in SCAN_ROOTS + for name in REQUIRED_SCAN_ROOTS if not (root / name).is_dir() ] + legacy_samples = root / "samples" + if legacy_samples.is_dir(): + errors.extend( + f"{path.relative_to(root).as_posix()}: " + "legacy samples directory must not contain tracked public content" + for path in sorted(legacy_samples.rglob("*")) + if path.is_file() + ) count = 0 for path in _text_files(root): count += 1 diff --git a/scripts/docs/validate_search_index.py b/scripts/docs/validate_search_index.py index 0b0570b..b692b4f 100644 --- a/scripts/docs/validate_search_index.py +++ b/scripts/docs/validate_search_index.py @@ -18,7 +18,8 @@ if str(REPO_ROOT) not in sys.path: sys.path.insert(0, str(REPO_ROOT)) -from scripts.docs.content import iter_public_documents, load_taxonomy +from scripts.docs.content import load_taxonomy +from scripts.docs.topics import iter_topic_documents KOREAN_WORD = re.compile(r"[가-힣]{2,}") @@ -90,7 +91,7 @@ def _representative_match(pattern: re.Pattern[str], body: str, title: str) -> st def validate_repository(repo_root: Path | str) -> SearchIndexResult: root = Path(repo_root) taxonomy = load_taxonomy(root / "docs-taxonomy.yml") - documents = list(iter_public_documents(root / "docs", taxonomy)) + documents = list(iter_topic_documents(root / "docs", taxonomy)) index_path = root / "site" / "search" / "search_index.json" if not index_path.is_file(): return SearchIndexResult( diff --git a/scripts/docs/validate_sources.py b/scripts/docs/validate_sources.py index a9c13bc..9542bed 100644 --- a/scripts/docs/validate_sources.py +++ b/scripts/docs/validate_sources.py @@ -14,10 +14,10 @@ from scripts.docs.content import ( DocumentFormatError, ValidationResult, - iter_public_documents, load_taxonomy, validate_source_metadata, ) +from scripts.docs.topics import iter_topic_documents def validate_repository(repo_root: Path | str, today: date | None = None) -> ValidationResult: @@ -27,7 +27,7 @@ def validate_repository(repo_root: Path | str, today: date | None = None) -> Val errors: list[str] = [] count = 0 try: - documents = list(iter_public_documents(docs_dir, taxonomy)) + documents = list(iter_topic_documents(docs_dir, taxonomy)) except DocumentFormatError as error: return ValidationResult(0, [str(error)]) for document in documents: diff --git a/tests/docs/test_content.py b/tests/docs/test_content.py index f18280c..9805151 100644 --- a/tests/docs/test_content.py +++ b/tests/docs/test_content.py @@ -1,11 +1,12 @@ from __future__ import annotations from datetime import date -from pathlib import Path +from pathlib import Path, PurePosixPath import pytest from scripts.docs.content import ( + Document, DocumentFormatError, iter_public_documents, load_document, @@ -57,13 +58,13 @@ def test_valid_page_bundle_satisfies_the_content_contract( path = copy_fixture( tmp_path, "valid-guide.md", - "guides/aks/network-diagnosis/index.md", + "services/aks/network-diagnosis/index.md", ) document = load_document(path, docs_dir=tmp_path / "docs") assert document.relative_path.as_posix() == ( - "guides/aks/network-diagnosis/index.md" + "services/aks/network-diagnosis/index.md" ) assert document.metadata["sources_checked_at"] == date(2026, 9, 12) assert validate_document(document, taxonomy, today=date(2026, 9, 12)) == [] @@ -73,7 +74,7 @@ def test_page_bundle_requires_yaml_front_matter(tmp_path: Path) -> None: path = copy_fixture( tmp_path, "invalid-guide.md", - "guides/aks/no-front-matter/index.md", + "services/aks/no-front-matter/index.md", ) with pytest.raises(DocumentFormatError, match="YAML front matter"): @@ -86,7 +87,7 @@ def test_content_contract_rejects_invalid_path_taxonomy_and_source( path = copy_fixture( tmp_path, "valid-guide.md", - "guides/aks/Network_Diagnosis.md", + "services/aks/Network_Diagnosis.md", ) loaded = load_document(path, docs_dir=tmp_path / "docs") metadata = { @@ -106,7 +107,7 @@ def test_content_contract_rejects_invalid_path_taxonomy_and_source( ) expected = ( - "public documents must use ///index.md", + "public documents must use services//[/]/index.md", "unknown service: unknown", "invalid guide status: draft", "sources_checked_at cannot be in the future", @@ -123,7 +124,7 @@ def test_unverified_guide_can_leave_last_verified_empty( path = copy_fixture( tmp_path, "valid-guide.md", - "guides/aks/network-diagnosis/index.md", + "services/aks/network-diagnosis/index.md", ) loaded = load_document(path, docs_dir=tmp_path / "docs") metadata = { @@ -159,7 +160,7 @@ def test_invalid_metadata_values_return_validation_errors( tmp_path: Path, taxonomy: dict, field: str, value: object ) -> None: path = copy_fixture( - tmp_path, "valid-guide.md", "guides/aks/network-diagnosis/index.md" + tmp_path, "valid-guide.md", "services/aks/network-diagnosis/index.md" ) loaded = load_document(path, docs_dir=tmp_path / "docs") @@ -172,12 +173,27 @@ def test_invalid_metadata_values_return_validation_errors( assert any(field in error for error in errors), errors +def test_canonical_topic_paths_are_valid( + tmp_path: Path, taxonomy: dict +) -> None: + path = tmp_path / "docs" / "services" / "aks" / "network-diagnosis" / "index.md" + path.parent.mkdir(parents=True) + path.write_text((FIXTURES / "valid-guide.md").read_text(encoding="utf-8"), encoding="utf-8") + loaded = load_document(path, docs_dir=tmp_path / "docs") + + assert validate_document( + loaded.with_metadata({**loaded.metadata, "services": ["aks"]}), + taxonomy, + today=date(2026, 9, 12), + ) == [] + + @pytest.mark.parametrize("featured", [True, False]) def test_featured_is_optional_boolean( tmp_path: Path, taxonomy: dict, featured: bool ) -> None: path = copy_fixture( - tmp_path, "valid-guide.md", "guides/aks/network-diagnosis/index.md" + tmp_path, "valid-guide.md", "services/aks/network-diagnosis/index.md" ) loaded = load_document(path, docs_dir=tmp_path / "docs") @@ -192,7 +208,7 @@ def test_malformed_source_url_returns_a_validation_error( tmp_path: Path, taxonomy: dict ) -> None: path = copy_fixture( - tmp_path, "valid-guide.md", "guides/aks/network-diagnosis/index.md" + tmp_path, "valid-guide.md", "services/aks/network-diagnosis/index.md" ) loaded = load_document(path, docs_dir=tmp_path / "docs") diff --git a/tests/docs/test_indexes.py b/tests/docs/test_indexes.py index e5fc23a..e0c2f63 100644 --- a/tests/docs/test_indexes.py +++ b/tests/docs/test_indexes.py @@ -9,15 +9,22 @@ from datetime import date from html import escape +from html.parser import HTMLParser from pathlib import Path, PurePosixPath +import re from markdown import Markdown from mkdocs.config import load_config import pytest import yaml -from scripts.docs.content import Document -from scripts.docs.generate_indexes import build_home_page, build_index_pages +from scripts.docs.content import Document, load_taxonomy +from scripts.docs.generate_indexes import ( + build_home_page, + build_index_pages, + build_redirect_pages, +) +from scripts.docs.topics import build_topic_catalog ROOT = Path(__file__).parents[2] @@ -32,6 +39,96 @@ def doc(relative_path: str, **metadata: object) -> Document: ) +def write_topic_document( + docs_dir: Path, + relative_path: str, + title: str, + *, + tags: list[str], + topic_order: int | None = None, + featured: bool = False, + redirect_from: list[str] | None = None, + document_type: str = "guide", +) -> None: + metadata: dict[str, object] = { + "title": title, + "description": f"{title} description", + "document_type": document_type, + "services": ["azure-monitor"], + "technologies": ["kubernetes"], + "tags": tags, + "status": "current", + "verification_status": "verified", + "sources_checked_at": "2026-09-12", + "official_sources": [ + { + "title": "Azure Monitor documentation", + "url": "https://learn.microsoft.com/azure/azure-monitor/", + } + ], + "last_verified": "2026-09-12", + "review_cycle_days": 180, + "applies_to": ["Azure Monitor"], + } + if topic_order is not None: + metadata["topic_order"] = topic_order + if featured: + metadata["featured"] = True + if redirect_from is not None: + metadata["redirect_from"] = redirect_from + + path = docs_dir / relative_path + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + "---\n" + + yaml.safe_dump(metadata, allow_unicode=True, sort_keys=False).strip() + + "\n---\n\n" + + f"# {title}\n", + encoding="utf-8", + ) + + +def build_topic_fixture( + tmp_path: Path, + taxonomy: dict, + *, + include_redirect: bool = False, +) -> tuple[list[Document], object]: + docs_dir = tmp_path / "docs" + write_topic_document( + docs_dir, + "services/azure-monitor/new-topic/index.md", + "Topic title", + tags=["networking"], + redirect_from=["guides/azure-monitor/old-topic/index.md"] if include_redirect else None, + ) + write_topic_document( + docs_dir, + "services/azure-monitor/new-topic/setup/index.md", + "Setup child", + tags=["networking"], + topic_order=1, + featured=True, + ) + write_topic_document( + docs_dir, + "services/azure-monitor/new-topic/results/index.md", + "Results child", + tags=["monitoring"], + topic_order=2, + ) + write_topic_document( + docs_dir, + "services/azure-monitor/standalone-topic/index.md", + "Standalone topic", + tags=["networking"], + featured=True, + ) + + catalog = build_topic_catalog(docs_dir, taxonomy) + return list(catalog.documents), catalog + + @pytest.fixture def taxonomy() -> dict: return { @@ -175,6 +272,75 @@ def test_collection_service_landing_contains_only_its_own_documents(taxonomy: di assert "First case" in pages[PurePosixPath("services/azure-monitor/index.md")] +def test_legacy_service_indexes_include_redirected_documents_deterministically( + tmp_path: Path, taxonomy: dict +) -> None: + docs_dir = tmp_path / "docs" + write_topic_document( + docs_dir, + "services/azure-monitor/current-topic/index.md", + "Current topic", + tags=[], + ) + write_topic_document( + docs_dir, + "services/azure-monitor/moved-topic/index.md", + "Moved topic", + tags=[], + redirect_from=[ + "research/azure-hdinsight/old-topic/index.md", + "research/azure-hdinsight/another-old-topic/index.md", + ], + ) + write_topic_document( + docs_dir, + "services/azure-monitor/moved-topic/benchmark/index.md", + "Benchmark", + tags=[], + topic_order=1, + document_type="research", + redirect_from=[ + "guides/azure-monitor/old-benchmark/index.md", + "research/azure-hdinsight/old-benchmark/index.md", + ], + ) + catalog = build_topic_catalog(docs_dir, taxonomy) + + pages = build_index_pages(catalog.documents, taxonomy, catalog=catalog) + + legacy = pages[PurePosixPath("research/azure-hdinsight/index.md")] + assert legacy.count("[Moved topic](../../services/azure-monitor/moved-topic/index.md)") == 1 + assert legacy.count("[Benchmark](../../services/azure-monitor/moved-topic/benchmark/index.md)") == 1 + assert "SERVICES / 2 DOCUMENTS" in legacy + assert "Current topic" not in legacy + current = pages[PurePosixPath("guides/azure-monitor/index.md")] + assert current.count("[Current topic](../../services/azure-monitor/current-topic/index.md)") == 1 + assert current.count("[Benchmark](../../services/azure-monitor/moved-topic/benchmark/index.md)") == 1 + assert "SERVICES / 3 DOCUMENTS" in current + assert current.count('class="dg-doc-card dg-topic-card') == 1 + assert legacy.count('class="dg-doc-card dg-topic-card') == 1 + assert "old-benchmark" not in current + assert pages == build_index_pages(reversed(catalog.documents), taxonomy, catalog=catalog) + assert pages[PurePosixPath("guides/index.md")].count('class="dg-doc-card dg-topic-card') == 1 + assert pages[PurePosixPath("services/azure-monitor/index.md")].count('class="dg-doc-card dg-topic-card') == 1 + + +def test_repository_legacy_hdinsight_index_links_to_canonical_benchmark() -> None: + taxonomy = load_taxonomy(ROOT / "docs-taxonomy.yml") + catalog = build_topic_catalog(ROOT / "docs", taxonomy) + + pages = build_index_pages(catalog.documents, taxonomy, catalog=catalog) + + assert PurePosixPath("research/azure-hdinsight/index.md") in pages + legacy = pages[PurePosixPath("research/azure-hdinsight/index.md")] + assert "../../services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md" in legacy + assert "kafka-catchup-sku-fetch-benchmark/index.md" not in legacy + for redirect, canonical in catalog.redirects.items(): + service_index = redirect.parent.parent / "index.md" + assert service_index in pages + assert f"../../{canonical.as_posix()}" in pages[service_index] + + def test_collection_page_reflects_real_counts_and_wrapper_classes(taxonomy: dict) -> None: documents = [ doc( @@ -378,6 +544,8 @@ def test_service_label_falls_back_to_slug_when_taxonomy_omits_it(taxonomy: dict) guide_index = pages[PurePosixPath("guides/index.md")] assert "## unlisted-service { #service-unlisted-service }" in guide_index + assert PurePosixPath("services/unlisted-service/index.md") in pages + assert "[unlisted-service](unlisted-service/index.md)" in pages[PurePosixPath("services/index.md")] def test_tags_are_capped_at_three_and_labels_fall_back_to_slug( @@ -477,6 +645,135 @@ def test_generated_collection_page_renders_service_groups_and_links( assert '
    AKS 토픽' in html +def test_topic_cards_collapse_multi_document_topics_across_generated_indexes( + tmp_path: Path, taxonomy: dict +) -> None: + documents, catalog = build_topic_fixture(tmp_path, taxonomy) + + pages = build_index_pages(documents, taxonomy, catalog=catalog) + home = build_home_page(HOME_TEMPLATE, documents, taxonomy, catalog=catalog) + + for path in ( + PurePosixPath("guides/index.md"), + PurePosixPath("services/azure-monitor/index.md"), + PurePosixPath("articles/index.md"), + ): + page = pages[path] + assert page.count('class="dg-doc-card dg-topic-card') == 1 + assert "3개 문서" in page + assert "Setup child" not in page + assert "Results child" not in page + assert "Standalone topic" in page + + tag_page = pages[PurePosixPath("tags/networking.md")] + assert tag_page.count('class="dg-doc-card dg-topic-card') == 1 + assert "2 / 3개 문서 일치" in tag_page + assert "Setup child" not in tag_page + assert "Results child" not in tag_page + assert "Standalone topic" in tag_page + + assert home.count('class="dg-doc-card dg-topic-card') == 1 + assert "3개 문서" in home + assert "Setup child" not in home + assert "Results child" not in home + assert "Standalone topic" in home + assert "[Topic title](services/azure-monitor/new-topic/index.md)" in home + + +def test_compatibility_child_links_are_stacked_above_the_card_overlay( + tmp_path: Path, taxonomy: dict, markdown_renderer: Markdown +) -> None: + class Anchors(HTMLParser): + def __init__(self) -> None: + super().__init__() + self.links: list[dict[str, str | None]] = [] + + def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: + if tag == "a": + self.links.append(dict(attrs)) + + documents, catalog = build_topic_fixture(tmp_path, taxonomy) + pages = build_index_pages(documents, taxonomy, catalog=catalog) + parser = Anchors() + parser.feed(render_body(markdown_renderer, pages[PurePosixPath("guides/azure-monitor/index.md")])) + children = [ + link for link in parser.links + if link.get("href") in { + "../../services/azure-monitor/new-topic/setup/index.md", + "../../services/azure-monitor/new-topic/results/index.md", + } + ] + assert len(children) == 2 + child_class = "dg-topic-child-link" + for child in children: + assert child_class in (child.get("class") or "").split() + assert child.get("tabindex", "0") == "0" + + css = (ROOT / "docs/assets/stylesheets/extra.css").read_text(encoding="utf-8") + rules = re.findall(r"([^{}]+)\{([^{}]+)\}", css) + stacking = next( + ( + body for selectors, body in rules + if f".dg-doc-card .{child_class}" in { + selector.strip() for selector in selectors.split(",") + } + ), + "", + ) + assert re.search(r"position:\s*relative\s*;", stacking) + assert re.search(r"z-index:\s*1\s*;", stacking) + overlay = next(body for selectors, body in rules if "a::after" in selectors) + assert re.search(r"position:\s*absolute\s*;", overlay) + assert re.search(r"inset:\s*0\s*;", overlay) + overlay_level = re.search(r"z-index:\s*([^;]+);", overlay) + assert ( + overlay_level is None + or overlay_level.group(1).strip() == "auto" + or int(overlay_level.group(1)) < 1 + ) + assert ".md-typeset a:focus-visible" in css + + +def test_redirect_pages_point_to_canonical_topic_entries_and_stay_out_of_indexes( + tmp_path: Path, taxonomy: dict +) -> None: + documents, catalog = build_topic_fixture(tmp_path, taxonomy, include_redirect=True) + + redirect_path = PurePosixPath("guides/azure-monitor/old-topic/index.md") + redirect_pages = build_redirect_pages(catalog) + redirect_page = redirect_pages[redirect_path] + + assert yaml.safe_load(redirect_page.split("---", 2)[1]) == { + "title": "문서 이동", + "search": {"exclude": True}, + "hide": ["navigation", "toc"], + } + assert '' in redirect_page + assert 'http-equiv="refresh"' in redirect_page + assert 'content="0; url=../../../services/azure-monitor/new-topic/"' in redirect_page + assert 'href="../../../services/azure-monitor/new-topic/"' in redirect_page + assert "services/azure-monitor/new-topic/index.md" in redirect_page + + pages = build_index_pages(documents, taxonomy, catalog=catalog) + assert all("old-topic" not in content for content in pages.values()) + + +def test_secondary_service_pages_show_the_matching_service_on_topic_cards( + tmp_path: Path, taxonomy: dict +) -> None: + documents, catalog = build_topic_fixture(tmp_path, taxonomy) + for document in documents: + if document.relative_path == PurePosixPath("services/azure-monitor/new-topic/setup/index.md"): + document.metadata["services"] = ["azure-monitor", "azure-kubernetes-service"] + break + + pages = build_index_pages(documents, taxonomy, catalog=catalog) + service_page = pages[PurePosixPath("services/azure-kubernetes-service/index.md")] + + assert "Azure Kubernetes Service" in service_page + assert "Azure Monitor1 / 3개 문서 일치" not in service_page + + # --------------------------------------------------------------------------- # build_index_pages: service pages # --------------------------------------------------------------------------- diff --git a/tests/docs/test_public_safety.py b/tests/docs/test_public_safety.py index 929184f..e0041c6 100644 --- a/tests/docs/test_public_safety.py +++ b/tests/docs/test_public_safety.py @@ -22,21 +22,21 @@ def test_git_ignores_local_state_without_hiding_shared_inputs(tmp_path: Path) -> local_paths = { ".azure/deployment-plan.md", ".azure/validate-status.json", - "samples/example/.azure/environment.json", + "docs/services/example/topic/samples/example/.azure/environment.json", ".claude/settings.local.json", ".DS_Store", - "docs/guides/example/.DS_Store", + "docs/services/example/topic/.DS_Store", "sim-env.json", - "samples/example/sim-env.json", - "samples/azure-monitor/source-material/sre-agent-event-lab/evidence/run.json", + "docs/services/example/topic/samples/example/sim-env.json", + "docs/services/azure-monitor/azure-sre-agent/samples/event-lab/evidence/run.json", *COMPLETED_PLAN_PATHS, } shared_paths = { ".vscode/mcp.json", ".devcontainer/devcontainer.json", - "samples/example/.env.example", - "samples/example/infra/main.parameters.json", - "samples/example/assets/captures/report.md", + "docs/services/example/topic/samples/example/.env.example", + "docs/services/example/topic/samples/example/infra/main.parameters.json", + "docs/services/example/topic/samples/example/assets/captures/report.md", "project/specs/maintained-design.md", } result = subprocess.run( @@ -66,8 +66,9 @@ def test_repository_does_not_track_local_artifacts() -> None: def make_repository(tmp_path: Path) -> Path: - (tmp_path / "docs" / "guides" / "aks" / "example").mkdir(parents=True) - (tmp_path / "samples" / "aks" / "example").mkdir(parents=True) + topic = tmp_path / "docs" / "services" / "aks" / "example" + topic.mkdir(parents=True) + (topic / "samples" / "example").mkdir(parents=True) return tmp_path @@ -75,7 +76,8 @@ def test_public_safety_accepts_placeholders_and_example_hosts( tmp_path: Path, ) -> None: root = make_repository(tmp_path) - (root / "docs" / "guides" / "aks" / "example" / "index.md").write_text( + assert not (root / "samples").exists() + (root / "docs" / "services" / "aks" / "example" / "index.md").write_text( """\ # Safe example @@ -94,12 +96,31 @@ def test_public_safety_accepts_placeholders_and_example_hosts( assert result.errors == [] +def test_public_safety_rejects_legacy_top_level_sample_content(tmp_path: Path) -> None: + root = make_repository(tmp_path) + (root / "docs" / "services" / "aks" / "example" / "index.md").write_text( + "# Safe example\n", encoding="utf-8" + ) + legacy_sample = root / "samples" / "example" + legacy_sample.mkdir(parents=True) + (legacy_sample / "README.md").write_text("# Legacy sample\n", encoding="utf-8") + + result = validate_public_safety.validate_repository(root) + + assert result.file_count == 1 + assert any( + "samples/example/README.md: legacy samples directory must not contain tracked public content" + in error + for error in result.errors + ) + + def test_public_safety_scans_supported_text_and_detects_sensitive_values( tmp_path: Path, ) -> None: root = make_repository(tmp_path) - docs = root / "docs" / "guides" / "aks" / "example" - samples = root / "samples" / "aks" / "example" + docs = root / "docs" / "services" / "aks" / "example" + samples = docs / "samples" / "example" (docs / "index.md").write_text( """\ # Unsafe example @@ -140,20 +161,19 @@ def test_public_safety_scans_supported_text_and_detects_sensitive_values( assert any("file is not valid UTF-8" in error for error in result.errors) -def test_public_safety_fails_closed_for_missing_or_empty_scan_roots( +def test_public_safety_fails_closed_for_missing_or_empty_docs_root( tmp_path: Path, ) -> None: - missing_samples = tmp_path / "missing-samples" - (missing_samples / "docs").mkdir(parents=True) + missing_docs = tmp_path / "missing-docs" + missing_docs.mkdir() empty = tmp_path / "empty" (empty / "docs").mkdir(parents=True) - (empty / "samples").mkdir() - missing_result = validate_public_safety.validate_repository(missing_samples) + missing_result = validate_public_safety.validate_repository(missing_docs) empty_result = validate_public_safety.validate_repository(empty) assert any( - "samples" in error and "missing" in error + "docs" in error and "missing" in error for error in missing_result.errors ) assert any("no public text files" in error for error in missing_result.errors) @@ -163,10 +183,12 @@ def test_public_safety_fails_closed_for_missing_or_empty_scan_roots( @pytest.mark.parametrize("name", [".env", ".ENV"]) def test_public_safety_scans_bare_dotenv_files(tmp_path: Path, name: str) -> None: root = make_repository(tmp_path) - (root / "docs" / "guides" / "aks" / "example" / "index.md").write_text( + (root / "docs" / "services" / "aks" / "example" / "index.md").write_text( "# Safe example\n", encoding="utf-8" ) - environment = root / "samples" / name + environment = ( + root / "docs" / "services" / "aks" / "example" / "samples" / "example" / name + ) environment.write_text( "SEARCH_ENDPOINT=https://private-search-123.search.windows.net\n", encoding="utf-8", @@ -176,7 +198,8 @@ def test_public_safety_scans_bare_dotenv_files(tmp_path: Path, name: str) -> Non assert result.file_count == 2 assert any( - f"samples/{name}:1:" in error and "non-example Azure service hostname" in error + f"docs/services/aks/example/samples/example/{name}:1:" in error + and "non-example Azure service hostname" in error for error in result.errors ) diff --git a/tests/docs/test_reader_navigation.py b/tests/docs/test_reader_navigation.py index fa1efcc..8eed207 100644 --- a/tests/docs/test_reader_navigation.py +++ b/tests/docs/test_reader_navigation.py @@ -15,6 +15,7 @@ from scripts.docs.content import Document from scripts.docs.generate_indexes import build_home_page, build_index_pages from scripts.docs.hooks import on_page_markdown +from scripts.docs.topics import build_topic_catalog @pytest.fixture @@ -45,6 +46,139 @@ def document(collection: str, topic: str, title: str, tags: list[str]) -> Docume ) +def canonical_document( + document_type: str, topic: str, title: str, tags: list[str] +) -> Document: + relative_path = PurePosixPath("services") / "azure-monitor" / topic / "index.md" + return Document( + path=Path(relative_path), + relative_path=relative_path, + metadata={ + "title": title, + "description": f"{title} 설명", + "document_type": document_type, + "services": ["azure-monitor", "azure-storage"], + "tags": tags, + }, + body="networking이라는 단어를 본문에서 사용합니다.", + ) + + +def write_topic_document( + docs_dir: Path, + relative_path: str, + title: str, + *, + tags: list[str], + topic_order: int | None = None, +) -> None: + metadata: dict[str, object] = { + "title": title, + "description": f"{title} 설명", + "document_type": "guide", + "services": ["azure-monitor"], + "technologies": ["kubernetes"], + "tags": tags, + "status": "current", + "verification_status": "verified", + "sources_checked_at": "2026-09-12", + "official_sources": [ + { + "title": "Azure Monitor documentation", + "url": "https://learn.microsoft.com/azure/azure-monitor/", + } + ], + "last_verified": "2026-09-12", + "review_cycle_days": 180, + "applies_to": ["Azure Monitor"], + } + if topic_order is not None: + metadata["topic_order"] = topic_order + + path = docs_dir / relative_path + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + "---\n" + + yaml.safe_dump(metadata, allow_unicode=True, sort_keys=False).strip() + + "\n---\n\n" + + f"# {title}\n\n본문입니다.\n", + encoding="utf-8", + ) + + +def build_topic_fixture(tmp_path: Path, taxonomy: dict) -> tuple[Path, object]: + docs_dir = tmp_path / "docs" + docs_dir.mkdir() + (tmp_path / "docs-taxonomy.yml").write_text( + yaml.safe_dump(taxonomy, allow_unicode=True, sort_keys=False), + encoding="utf-8", + ) + write_topic_document( + docs_dir, + "services/azure-monitor/new-topic/index.md", + "Topic title", + tags=["networking"], + ) + write_topic_document( + docs_dir, + "services/azure-monitor/new-topic/setup/index.md", + "Setup child", + tags=["networking"], + topic_order=1, + ) + write_topic_document( + docs_dir, + "services/azure-monitor/new-topic/results/index.md", + "Results child", + tags=["monitoring"], + topic_order=2, + ) + write_topic_document( + docs_dir, + "services/azure-monitor/standalone-topic/index.md", + "Standalone topic", + tags=["networking"], + ) + + sample_dir = docs_dir / "services" / "azure-monitor" / "new-topic" / "samples" / "event-lab" + sample_dir.mkdir(parents=True, exist_ok=True) + (sample_dir / "sample.yml").write_text( + yaml.safe_dump( + { + "title": "Event lab", + "description": "Reproduces the monitored incident.", + "kind": "runnable", + "used_by": ["setup"], + }, + allow_unicode=True, + sort_keys=False, + ), + encoding="utf-8", + ) + (sample_dir / "README.md").write_text("# Event lab\n", encoding="utf-8") + + return docs_dir, build_topic_catalog(docs_dir, taxonomy) + + +def page_namespace(document: Document, *, previous: Document | None = None, next_: Document | None = None): + def nav_item(item: Document | None): + if item is None: + return None + return SimpleNamespace( + title=item.metadata["title"], + url=item.relative_path.as_posix().removesuffix("index.md"), + ) + + return SimpleNamespace( + title=document.metadata["title"], + meta=document.metadata, + file=SimpleNamespace(src_uri=str(document.relative_path)), + url=document.relative_path.as_posix().removesuffix("index.md"), + previous_page=nav_item(previous), + next_page=nav_item(next_), + ) + + class Links(HTMLParser): def __init__(self) -> None: super().__init__() @@ -202,6 +336,89 @@ def test_article_tags_use_the_same_detail_pages(tmp_path: Path, taxonomy: dict) assert "본문입니다." in rendered +def test_topic_pages_render_context_outline_and_related_samples( + tmp_path: Path, taxonomy: dict +) -> None: + docs_dir, catalog = build_topic_fixture(tmp_path, taxonomy) + entry = next( + document + for document in catalog.documents + if document.relative_path == PurePosixPath("services/azure-monitor/new-topic/index.md") + ) + setup = next( + document + for document in catalog.documents + if document.relative_path == PurePosixPath("services/azure-monitor/new-topic/setup/index.md") + ) + results = next( + document + for document in catalog.documents + if document.relative_path == PurePosixPath("services/azure-monitor/new-topic/results/index.md") + ) + standalone = next( + document + for document in catalog.documents + if document.relative_path + == PurePosixPath("services/azure-monitor/standalone-topic/index.md") + ) + config = { + "docs_dir": str(docs_dir), + "repo_url": "https://github.com/example/devguidesample", + } + + entry_rendered = on_page_markdown( + "# Topic title\n\n본문입니다.\n", + page_namespace(entry, next_=setup), + config, + None, + ) + setup_rendered = on_page_markdown( + "# Setup child\n\n본문입니다.\n", + page_namespace(setup, previous=entry, next_=results), + config, + None, + ) + results_rendered = on_page_markdown( + "# Results child\n\n본문입니다.\n", + page_namespace(results, previous=setup), + config, + None, + ) + standalone_rendered = on_page_markdown( + "# Standalone topic\n\n본문입니다.\n", + page_namespace(standalone), + config, + None, + ) + + assert 'class="dg-topic-overview"' in entry_rendered + assert "TOPIC · 3 DOCUMENTS" in entry_rendered + assert "Topic title · 1 / 3" in entry_rendered + assert entry_rendered.count("Setup child") == 1 + assert entry_rendered.count("Results child") == 1 + assert entry_rendered.index("Setup child") < entry_rendered.index("Results child") + + assert 'class="dg-topic-context"' in setup_rendered + assert "Topic title · 2 / 3" in setup_rendered + assert ">전체 목차<" in setup_rendered + assert 'class="dg-topic-nav"' in setup_rendered + assert setup_rendered.index("본문입니다.") < setup_rendered.index('class="dg-topic-nav"') + assert 'class="dg-sample-card"' in setup_rendered + assert "Event lab" in setup_rendered + assert ( + "https://github.com/example/devguidesample/tree/main/" + "docs/services/azure-monitor/new-topic/samples/event-lab" + ) in setup_rendered + assert setup_rendered.index('class="dg-sample-card"') < setup_rendered.index( + 'class="doc-sources"' + ) + + assert "Topic title · 3 / 3" in results_rendered + assert 'class="dg-topic-nav"' in results_rendered + assert 'class="dg-sample-card"' not in results_rendered + assert 'class="dg-sample-card"' not in standalone_rendered + + class ReaderPage(HTMLParser): def __init__(self, path: Path) -> None: super().__init__() @@ -323,7 +540,9 @@ def add_bundle(item: Document) -> Path: ) return path - original = add_bundle(document("guides", "setup", "B 구성 절차", ["networking"])) + original = add_bundle( + canonical_document("guide", "setup", "B 구성 절차", ["networking"]) + ) unchanged = [settings_path, navigation_path, taxonomy_path, docs / "index.md", original] original_bytes = [path.read_bytes() for path in unchanged] build(load_config(str(settings_path), strict=True)) @@ -331,11 +550,15 @@ def add_bundle(item: Document) -> Path: assert list(home.root_links.values()) == ["홈", "서비스별 보기", "태그별 보기", "전체 글", "기여하기"] assert home.navigation_links["services/azure-monitor/"] == "Azure Monitor" - assert home.navigation_links["guides/azure-monitor/setup/"] == "B 구성 절차" + assert home.navigation_links["services/azure-monitor/setup/"] == "B 구성 절차" assert home.expanded_groups == 0 assert not (tmp_path / "site/tags/latency/index.html").exists() - add_bundle(document("research", "choices", "A 선택 근거", ["networking", "latency"])) + add_bundle( + canonical_document( + "research", "choices", "A 선택 근거", ["networking", "latency"] + ) + ) build(load_config(str(settings_path), strict=True)) assert [path.read_bytes() for path in unchanged] == original_bytes @@ -345,18 +568,21 @@ def add_bundle(item: Document) -> Path: new_tag = ReaderPage(tmp_path / "site/tags/latency/index.html") assert [title for target, title in new_tag.card_entries] == ["A 선택 근거"] updated_home = ReaderPage(tmp_path / "site/index.html") - assert updated_home.navigation_links["research/azure-monitor/choices/"] == "A 선택 근거" + assert updated_home.navigation_links["services/azure-monitor/choices/"] == "A 선택 근거" sidebar_documents = [ entry for entry in updated_home.navigation_entries - if entry[0].startswith(("guides/", "research/")) + if entry[0].startswith("services/azure-monitor/") + and entry[0] != "services/azure-monitor/" ] primary_service = ("services/", "services/azure-monitor/") assert sidebar_documents == [ - ("research/azure-monitor/choices/", "A 선택 근거", primary_service), - ("guides/azure-monitor/setup/", "B 구성 절차", primary_service), + ("services/azure-monitor/choices/", "A 선택 근거", primary_service), + ("services/azure-monitor/setup/", "B 구성 절차", primary_service), ] - add_bundle(document("guides", "mentions", "C 본문만 일치", ["monitoring"])) + add_bundle( + canonical_document("guide", "mentions", "C 본문만 일치", ["monitoring"]) + ) build(load_config(str(settings_path), strict=True)) assert [path.read_bytes() for path in unchanged] == original_bytes @@ -368,11 +594,12 @@ def add_bundle(item: Document) -> Path: final_home = ReaderPage(tmp_path / "site/index.html") assert [ entry for entry in final_home.navigation_entries - if entry[0].startswith(("guides/", "research/")) + if entry[0].startswith("services/azure-monitor/") + and entry[0] != "services/azure-monitor/" ] == [ - ("research/azure-monitor/choices/", "A 선택 근거", primary_service), - ("guides/azure-monitor/setup/", "B 구성 절차", primary_service), - ("guides/azure-monitor/mentions/", "C 본문만 일치", primary_service), + ("services/azure-monitor/choices/", "A 선택 근거", primary_service), + ("services/azure-monitor/setup/", "B 구성 절차", primary_service), + ("services/azure-monitor/mentions/", "C 본문만 일치", primary_service), ] tag = ReaderPage(tmp_path / "site/tags/networking/index.html") @@ -382,8 +609,8 @@ def add_bundle(item: Document) -> Path: assert (tmp_path / "site/guides/azure-monitor/index.html").is_file() assert (tmp_path / "site/research/index.html").is_file() - article = ReaderPage(tmp_path / "site/guides/azure-monitor/setup/index.html") - article_url = "https://example.test/devguidesample/guides/azure-monitor/setup/" + article = ReaderPage(tmp_path / "site/services/azure-monitor/setup/index.html") + article_url = "https://example.test/devguidesample/services/azure-monitor/setup/" assert {urljoin(article_url, target) for target in article.tag_links} == { "https://example.test/devguidesample/tags/networking/" } @@ -396,4 +623,9 @@ def add_bundle(item: Document) -> Path: search = json.loads((tmp_path / "site/search/search_index.json").read_text(encoding="utf-8")) locations = {entry["location"] for entry in search["docs"]} - assert {"tags/networking/", "tags/latency/", "articles/", "research/azure-monitor/choices/"} <= locations + assert { + "tags/networking/", + "tags/latency/", + "articles/", + "services/azure-monitor/choices/", + } <= locations diff --git a/tests/docs/test_site_pipeline.py b/tests/docs/test_site_pipeline.py index 3b6e96c..bbe9139 100644 --- a/tests/docs/test_site_pipeline.py +++ b/tests/docs/test_site_pipeline.py @@ -8,6 +8,7 @@ from urllib.parse import urljoin from markdown import Markdown +from mkdocs.structure.files import InclusionLevel from material.plugins.search.plugin import SearchIndex from mkdocs.commands.build import build from mkdocs.config import load_config @@ -16,9 +17,10 @@ import yaml from scripts.docs import validate_search_index -from scripts.docs.content import load_document +from scripts.docs.content import iter_public_documents, load_document from scripts.docs.generate_indexes import build_index_pages -from scripts.docs.hooks import on_page_markdown +from scripts.docs.hooks import on_files, on_page_markdown, on_post_page +from scripts.docs.topics import build_topic_catalog FIXTURE = Path(__file__).parent / "fixtures" / "valid-guide.md" @@ -48,6 +50,212 @@ """ +def write_topic_document( + docs_dir: Path, + relative_path: str, + title: str, + *, + tags: list[str], + topic_order: int | None = None, + featured: bool = False, + redirect_from: list[str] | None = None, +) -> None: + metadata: dict[str, object] = { + "title": title, + "description": f"{title} 설명", + "document_type": "guide", + "services": ["aks"], + "technologies": ["kubernetes"], + "tags": tags, + "status": "current", + "verification_status": "verified", + "sources_checked_at": "2026-09-12", + "official_sources": [ + { + "title": "Azure Kubernetes Service documentation", + "url": "https://learn.microsoft.com/azure/aks/", + } + ], + "last_verified": "2026-09-12", + "review_cycle_days": 180, + "applies_to": ["AKS 1.34+"], + } + if topic_order is not None: + metadata["topic_order"] = topic_order + if featured: + metadata["featured"] = True + if redirect_from is not None: + metadata["redirect_from"] = redirect_from + + path = docs_dir / relative_path + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + "---\n" + + yaml.safe_dump(metadata, allow_unicode=True, sort_keys=False).strip() + + "\n---\n\n" + + f"# {title}\n\n{title} 본문입니다.\n", + encoding="utf-8", + ) + + +class TopicReaderPage(HTMLParser): + def __init__(self, path: Path) -> None: + super().__init__() + self.navigation_depth = 0 + self.navigation_parents: list[str | None] = [] + self.last_navigation_target: str | None = None + self.navigation_entries: list[tuple[str, str, tuple[str, ...]]] = [] + self.expanded_groups = 0 + self.topic_nav_depth = 0 + self.topic_nav_links: list[tuple[str, str]] = [] + self.current_link: tuple[str, str] | None = None + self.link_text: list[str] = [] + self.feed(path.read_text(encoding="utf-8")) + + def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: + attributes = dict(attrs) + classes = (attributes.get("class") or "").split() + if tag == "nav" and (self.navigation_depth or "md-nav--primary" in classes): + self.navigation_depth += 1 + self.navigation_parents.append(self.last_navigation_target) + if tag == "nav" and "dg-topic-nav" in classes: + self.topic_nav_depth += 1 + if self.navigation_depth and tag == "input" and "md-nav__toggle" in classes: + self.expanded_groups += "checked" in attributes + target = attributes.get("href") + if tag != "a" or not isinstance(target, str): + return + self.current_link = ( + target, + "navigation" if self.navigation_depth else "topic" if self.topic_nav_depth else "other", + ) + self.link_text = [] + + def handle_endtag(self, tag: str) -> None: + if tag == "a" and self.current_link is not None: + target, kind = self.current_link + title = " ".join("".join(self.link_text).split()) + if kind == "navigation": + parents = tuple(parent for parent in self.navigation_parents if parent is not None) + self.navigation_entries.append((target, title, parents)) + self.last_navigation_target = target + elif kind == "topic": + self.topic_nav_links.append((target, title)) + self.current_link = None + if tag == "nav" and self.topic_nav_depth: + self.topic_nav_depth -= 1 + if tag == "nav" and self.navigation_depth: + self.navigation_depth -= 1 + self.navigation_parents.pop() + + def handle_data(self, data: str) -> None: + if self.current_link is not None: + self.link_text.append(data) + + +def make_topic_repository(tmp_path: Path) -> Path: + root = tmp_path + docs = root / "docs" + docs.mkdir() + (docs / "index.md").write_text( + "# Home\n\n\n\n\n\n\n", + encoding="utf-8", + ) + (docs / ".nav.yml").write_text( + "nav:\n - Home: index.md\n - Services: services\n - Tags: tags\n" + " - Articles: articles\n - glob: '*'\n ignore_no_matches: true\n", + encoding="utf-8", + ) + taxonomy = { + "collections": {"guide": {"path": "guides", "title": "구현 가이드"}}, + "services": {"aks": "Azure Kubernetes Service"}, + "technologies": {"kubernetes": "Kubernetes"}, + "tags": {"networking": "Networking", "monitoring": "Monitoring"}, + "verification_statuses": ["verified", "needs-review"], + "official_source_hosts": ["learn.microsoft.com"], + "required_source_host": "learn.microsoft.com", + } + (root / "docs-taxonomy.yml").write_text( + yaml.safe_dump(taxonomy, allow_unicode=True, sort_keys=False), + encoding="utf-8", + ) + site_config = yaml.safe_load( + (Path(__file__).parents[2] / "mkdocs.yml").read_text(encoding="utf-8") + ) + config_path = root / "mkdocs.yml" + site_config.update( + { + "site_name": "Topic test", + "site_url": "https://example.test/devguidesample/", + "repo_url": "https://github.com/example/devguidesample", + "docs_dir": str(docs), + "site_dir": str(root / "site"), + } + ) + generator = root / "generate.py" + generator.write_text( + "from pathlib import Path\n" + "from scripts.docs.generate_indexes import write_generated_pages\n" + f"write_generated_pages(Path({str(root)!r}))\n", + encoding="utf-8", + ) + site_config["hooks"] = [str(Path(__file__).parents[2] / "scripts" / "docs" / "hooks.py")] + site_config["plugins"] = [ + {"search": {"lang": ["ko", "en"]}}, + {"tags": {"tags": False, "listings": False}}, + {"gen-files": {"scripts": [str(generator)]}}, + "awesome-nav", + ] + config_path.write_text(yaml.safe_dump(site_config), encoding="utf-8") + + write_topic_document( + docs, + "services/aks/network-diagnosis/index.md", + "Topic title", + tags=["networking"], + redirect_from=["guides/aks/old-topic/index.md"], + ) + write_topic_document( + docs, + "services/aks/network-diagnosis/setup/index.md", + "Setup child", + tags=["networking"], + topic_order=1, + featured=True, + ) + write_topic_document( + docs, + "services/aks/network-diagnosis/results/index.md", + "Results child", + tags=["monitoring"], + topic_order=2, + ) + write_topic_document( + docs, + "services/aks/standalone-topic/index.md", + "Standalone topic", + tags=["networking"], + featured=True, + ) + sample_dir = docs / "services" / "aks" / "network-diagnosis" / "samples" / "event-lab" + sample_dir.mkdir(parents=True, exist_ok=True) + (sample_dir / "sample.yml").write_text( + yaml.safe_dump( + { + "title": "Event lab", + "description": "Reproduces the monitored incident.", + "kind": "runnable", + "used_by": ["setup"], + }, + allow_unicode=True, + sort_keys=False, + ), + encoding="utf-8", + ) + (sample_dir / "README.md").write_text("# Event lab\n", encoding="utf-8") + return root + + def test_indexes_are_generated_from_metadata_with_safe_yaml() -> None: loaded = load_document(FIXTURE, docs_dir=FIXTURE.parent) document = loaded.__class__( @@ -123,6 +331,36 @@ def test_mkdocs_keeps_navigation_and_search_metadata_driven() -> None: if isinstance(plugin, dict) and "search" in plugin ) assert search["lang"] == ["ko", "en"] + assert config["exclude_docs"].strip() == "services/**/samples/**" + + +def test_topic_and_sample_styles_are_responsive_and_accessible() -> None: + css = ( + Path(__file__).parents[2] / "docs/assets/stylesheets/extra.css" + ).read_text(encoding="utf-8") + + for selector in ( + ".dg-topic-card", + ".dg-topic-overview", + ".dg-topic-list", + ".dg-topic-context", + ".dg-topic-nav", + ".dg-sample-grid", + ".dg-sample-card", + ): + assert selector in css + + mobile = css.split("@media (max-width: 760px)", 1)[1].split( + "@media (max-width: 480px)", 1 + )[0] + assert ".dg-topic-nav" in mobile + assert ".dg-sample-grid" in mobile + assert "grid-template-columns: minmax(0, 1fr)" in mobile + assert "a:focus-visible" in css + + reduced_motion = css.split("@media (prefers-reduced-motion: reduce)", 1)[1] + assert ".dg-sample-card" in reduced_motion + assert "transition: none" in reduced_motion def test_strict_build_rejects_missing_anchors( @@ -249,6 +487,96 @@ def test_page_hook_keeps_sources_without_workflow_notices() -> None: assert missing == "# Page\n" +def test_on_files_excludes_all_markdown_under_samples() -> None: + readme = SimpleNamespace( + src_uri="services/aks/network-diagnosis/samples/event-lab/README.md", + inclusion=InclusionLevel.UNDEFINED, + ) + notes = SimpleNamespace( + src_uri="services/aks/network-diagnosis/samples/event-lab/notes.md", + inclusion=InclusionLevel.UNDEFINED, + ) + regular = SimpleNamespace( + src_uri="services/aks/network-diagnosis/index.md", + inclusion=InclusionLevel.UNDEFINED, + ) + manifest = SimpleNamespace( + src_uri="services/aks/network-diagnosis/samples/event-lab/sample.yml", + inclusion=InclusionLevel.UNDEFINED, + ) + + on_files([readme, notes, manifest, regular], {}) + + assert readme.inclusion is InclusionLevel.EXCLUDED + assert notes.inclusion is InclusionLevel.EXCLUDED + assert manifest.inclusion is InclusionLevel.EXCLUDED + assert regular.inclusion is InclusionLevel.UNDEFINED + + +def test_topic_catalog_integration_preserves_public_path_filter(tmp_path: Path) -> None: + docs = tmp_path / "docs" + docs.mkdir() + taxonomy = { + "collections": {"guide": {"path": "guides", "title": "구현 가이드"}}, + "services": {"aks": "Azure Kubernetes Service"}, + "technologies": {"kubernetes": "Kubernetes"}, + "tags": {"networking": "Networking", "troubleshooting": "Troubleshooting"}, + "verification_statuses": ["verified", "needs-review"], + "official_source_hosts": ["learn.microsoft.com"], + "required_source_host": "learn.microsoft.com", + } + (tmp_path / "docs-taxonomy.yml").write_text( + yaml.safe_dump(taxonomy, allow_unicode=True, sort_keys=False), + encoding="utf-8", + ) + invalid = docs / "services" / "AKS" / "MixedTopic" + invalid.mkdir(parents=True) + (invalid / "index.md").write_text(SEARCH_DOCUMENT, encoding="utf-8") + + public_documents = list(iter_public_documents(docs, taxonomy)) + catalog = build_topic_catalog(docs, taxonomy, documents=public_documents) + pages = build_index_pages(catalog.documents, taxonomy, catalog=catalog) + + assert public_documents == [] + assert catalog.documents == () + assert "AKS 네트워크 진단" not in pages[PurePosixPath("articles/index.md")] + + +def test_on_post_page_injects_head_redirect_tags_when_theme_canonical_is_missing( + tmp_path: Path, +) -> None: + docs = tmp_path / "docs" + docs.mkdir() + taxonomy = { + "collections": {"guide": {"path": "guides", "title": "구현 가이드"}}, + "services": {"aks": "Azure Kubernetes Service"}, + "technologies": {"kubernetes": "Kubernetes"}, + "tags": {"networking": "Networking"}, + "verification_statuses": ["verified", "needs-review"], + "official_source_hosts": ["learn.microsoft.com"], + "required_source_host": "learn.microsoft.com", + } + (tmp_path / "docs-taxonomy.yml").write_text( + yaml.safe_dump(taxonomy, allow_unicode=True, sort_keys=False), + encoding="utf-8", + ) + canonical = docs / "services" / "aks" / "network-diagnosis" + canonical.mkdir(parents=True) + (canonical / "index.md").write_text( + SEARCH_DOCUMENT.replace( + "last_verified: 2026-09-12", + "redirect_from: [guides/aks/old-topic/index.md]\nlast_verified: 2026-09-12", + ), + encoding="utf-8", + ) + page = SimpleNamespace(file=SimpleNamespace(src_uri="guides/aks/old-topic/index.md")) + + rendered = on_post_page("", page, {"docs_dir": str(docs)}) + + assert '' in rendered + assert '' in rendered + + def make_search_repository(tmp_path: Path) -> Path: taxonomy = { "collections": {"guide": {"path": "guides"}}, @@ -275,7 +603,7 @@ def make_search_repository(tmp_path: Path) -> Path: ), encoding="utf-8", ) - page = tmp_path / "docs" / "guides" / "aks" / "network-diagnosis" + page = tmp_path / "docs" / "services" / "aks" / "network-diagnosis" page.mkdir(parents=True) (page / "index.md").write_text(SEARCH_DOCUMENT, encoding="utf-8") (tmp_path / "site" / "search").mkdir(parents=True) @@ -358,17 +686,17 @@ def handle_data(self, text: str) -> None: (root / "site" / "index.html").read_text(encoding="utf-8") ) - assert single_document_navigation.links["guides/aks/network-diagnosis/"] == "AKS 네트워크 진단" + assert single_document_navigation.links["services/aks/network-diagnosis/"] == "AKS 네트워크 진단" assert single_document_navigation.links["services/aks/"] == "Azure Kubernetes Service" - page = docs / "guides" / "aks" / "new-topic" / "index.md" + page = docs / "services" / "aks" / "new-topic" / "index.md" page.parent.mkdir(parents=True) page.write_text( SEARCH_DOCUMENT.replace("AKS 네트워크 진단", "자동 게시 확인") + "\n추가된 문서의 검색 본문입니다.\n", encoding="utf-8", ) - third_page = docs / "guides" / "aks" / "z-last-topic" / "index.md" + third_page = docs / "services" / "aks" / "z-last-topic" / "index.md" third_page.parent.mkdir(parents=True) third_page.write_text( SEARCH_DOCUMENT.replace("AKS 네트워크 진단", "세 번째 문서"), @@ -382,33 +710,86 @@ def handle_data(self, text: str) -> None: ) assert [path.read_bytes() for path in unchanged_paths] == original_settings - assert navigation.links["guides/aks/network-diagnosis/"] == "AKS 네트워크 진단" - assert navigation.links["guides/aks/new-topic/"] == "자동 게시 확인" - assert navigation.links["guides/aks/z-last-topic/"] == "세 번째 문서" + assert navigation.links["services/aks/network-diagnosis/"] == "AKS 네트워크 진단" + assert navigation.links["services/aks/new-topic/"] == "자동 게시 확인" + assert navigation.links["services/aks/z-last-topic/"] == "세 번째 문서" assert navigation.links["services/aks/"] == "Azure Kubernetes Service" assert "guides/" not in navigation.links assert navigation.links["articles/"] == "Articles" assert "자동 게시 확인" in (root / "site" / "guides" / "index.html").read_text(encoding="utf-8") assert any( - entry["location"] == "guides/aks/new-topic/" + entry["location"] == "services/aks/new-topic/" and "추가된 문서" in entry["text"] for entry in search["docs"] ) article_navigation = PrimaryNavigation() article_navigation.feed( - (root / "site" / "guides" / "aks" / "network-diagnosis" / "index.html").read_text( + (root / "site" / "services" / "aks" / "network-diagnosis" / "index.html").read_text( encoding="utf-8" ) ) - article_url = "https://example.test/guides/aks/network-diagnosis/" + article_url = "https://example.test/services/aks/network-diagnosis/" resolved_links = { urljoin(article_url, target): title for target, title in article_navigation.links.items() } assert resolved_links[article_url] == "AKS 네트워크 진단" assert resolved_links["https://example.test/services/aks/"] == "Azure Kubernetes Service" - assert resolved_links["https://example.test/guides/aks/new-topic/"] == "자동 게시 확인" - assert resolved_links["https://example.test/guides/aks/z-last-topic/"] == "세 번째 문서" + assert resolved_links["https://example.test/services/aks/new-topic/"] == "자동 게시 확인" + assert resolved_links["https://example.test/services/aks/z-last-topic/"] == "세 번째 문서" + + +def test_topic_packages_drive_sidebar_navigation_redirects_and_bounded_topic_links( + tmp_path: Path, +) -> None: + root = make_topic_repository(tmp_path) + build(load_config(str(root / "mkdocs.yml"), strict=True)) + + home = TopicReaderPage(root / "site" / "index.html") + sidebar_documents = [ + entry + for entry in home.navigation_entries + if entry[0].startswith("services/aks/") + ] + assert ("services/aks/network-diagnosis/", "Topic title", ("services/", "services/aks/")) in sidebar_documents + assert ( + "services/aks/network-diagnosis/setup/", + "1. Setup child", + ("services/", "services/aks/", "services/aks/network-diagnosis/"), + ) in sidebar_documents + assert ( + "services/aks/network-diagnosis/results/", + "2. Results child", + ("services/", "services/aks/", "services/aks/network-diagnosis/"), + ) in sidebar_documents + assert ("services/aks/standalone-topic/", "Standalone topic", ("services/", "services/aks/")) in sidebar_documents + assert all("old-topic" not in target for target, _, _ in sidebar_documents) + + entry_page = TopicReaderPage(root / "site" / "services" / "aks" / "network-diagnosis" / "index.html") + assert entry_page.topic_nav_links == [("setup/", "다음 문서")] + + middle_page = TopicReaderPage( + root / "site" / "services" / "aks" / "network-diagnosis" / "setup" / "index.html" + ) + assert middle_page.expanded_groups == 3 + assert middle_page.topic_nav_links == [("../", "이전 문서"), ("../results/", "다음 문서")] + + final_page = TopicReaderPage( + root / "site" / "services" / "aks" / "network-diagnosis" / "results" / "index.html" + ) + assert final_page.topic_nav_links == [("../setup/", "이전 문서")] + + redirect_page_path = root / "site" / "guides" / "aks" / "old-topic" / "index.html" + redirect_page = redirect_page_path.read_text(encoding="utf-8") + assert "services/aks/network-diagnosis/" in redirect_page + assert '' in redirect_page + assert '' in redirect_page + search = json.loads((root / "site" / "search" / "search_index.json").read_text(encoding="utf-8")) + locations = {entry["location"] for entry in search["docs"]} + assert "guides/aks/old-topic/" not in locations + assert not any("/samples/" in location for location in locations) + assert not any("Event lab" in entry.get("text", "") for entry in search["docs"]) + assert not (root / "site" / "services" / "aks" / "network-diagnosis" / "samples").exists() def write_search_index(root: Path, documents: list[dict[str, str]]) -> None: @@ -420,7 +801,7 @@ def write_search_index(root: Path, documents: list[dict[str, str]]) -> None: def page_search_entry(text: str) -> dict[str, str]: return { - "location": "guides/aks/network-diagnosis/", + "location": "services/aks/network-diagnosis/", "title": "AKS 네트워크 진단", "text": text, } @@ -506,7 +887,7 @@ def test_search_gate_accepts_visible_rendered_markdown( tmp_path: Path, body: str ) -> None: root = make_search_repository(tmp_path) - path = root / "docs" / "guides" / "aks" / "network-diagnosis" / "index.md" + path = root / "docs" / "services" / "aks" / "network-diagnosis" / "index.md" front_matter = SEARCH_DOCUMENT.split("\n# ", 1)[0] path.write_text(front_matter + "\n\n" + body, encoding="utf-8") document = load_document(path, docs_dir=root / "docs") @@ -519,7 +900,7 @@ def test_search_gate_accepts_visible_rendered_markdown( title=document.metadata["title"], content=renderer.convert(document.body), toc=[], - url="guides/aks/network-diagnosis/", + url="services/aks/network-diagnosis/", ) index = SearchIndex() index.add_entry_from_context(page) diff --git a/tests/docs/test_topics.py b/tests/docs/test_topics.py new file mode 100644 index 0000000..82a5bde --- /dev/null +++ b/tests/docs/test_topics.py @@ -0,0 +1,778 @@ +from __future__ import annotations + +from pathlib import Path, PurePosixPath +import re + +import pytest +import yaml + +from scripts.docs.content import ( + DocumentFormatError, + load_taxonomy, + validate_document, +) +from scripts.docs.topics import build_topic_catalog + + +ROOT = Path(__file__).parents[2] + + +@pytest.fixture +def taxonomy() -> dict: + return { + "collections": { + "guide": { + "path": "guides", + "statuses": ["current", "needs-review", "deprecated"], + "required_fields": [ + "last_verified", + "review_cycle_days", + "applies_to", + ], + } + }, + "services": {"azure-monitor": "Azure Monitor"}, + "technologies": {"kubernetes": "Kubernetes"}, + "tags": {"networking": "Networking"}, + "verification_statuses": ["verified", "needs-review"], + "official_source_hosts": ["learn.microsoft.com"], + "required_source_host": "learn.microsoft.com", + } + + +def write_document( + root: Path, + relative: str, + title: str, + *, + topic_order: int | None = None, + redirect_from: list[str] | None = None, + services: list[str] | None = None, +) -> Path: + metadata: dict[str, object] = { + "title": title, + "description": f"{title} description", + "document_type": "guide", + "services": services or ["azure-monitor"], + "technologies": ["kubernetes"], + "tags": ["networking"], + "status": "current", + "verification_status": "verified", + "sources_checked_at": "2026-09-12", + "official_sources": [ + { + "title": "Azure Monitor documentation", + "url": "https://learn.microsoft.com/azure/azure-monitor/", + } + ], + "last_verified": "2026-09-12", + "review_cycle_days": 180, + "applies_to": ["Azure Monitor"], + } + if topic_order is not None: + metadata["topic_order"] = topic_order + if redirect_from is not None: + metadata["redirect_from"] = redirect_from + + path = root / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + "---\n" + + yaml.safe_dump(metadata, allow_unicode=True, sort_keys=False).strip() + + "\n---\n\n" + + f"# {title}\n", + encoding="utf-8", + ) + return path + + +def write_sample( + root: Path, + relative: str, + manifest: dict[str, object] | None = None, + *, + readme: bool = True, +) -> Path: + sample_dir = root / relative + sample_dir.mkdir(parents=True, exist_ok=True) + if manifest is not None: + (sample_dir / "sample.yml").write_text( + yaml.safe_dump(manifest, allow_unicode=True, sort_keys=False), + encoding="utf-8", + ) + if readme: + (sample_dir / "README.md").write_text( + "# Sample\n", + encoding="utf-8", + ) + return sample_dir + + +def test_build_topic_catalog_discovers_canonical_topic_documents_and_samples( + tmp_path: Path, taxonomy: dict +) -> None: + docs_dir = tmp_path / "docs" + write_document( + docs_dir, + "services/azure-monitor/agent-topic/index.md", + "Agent topic", + ) + write_document( + docs_dir, + "services/azure-monitor/agent-topic/setup/index.md", + "Setup", + topic_order=1, + ) + write_document( + docs_dir, + "services/azure-monitor/agent-topic/results/index.md", + "Results", + topic_order=2, + ) + write_sample( + docs_dir, + "services/azure-monitor/agent-topic/samples/event-lab", + { + "title": "Event lab", + "description": "Reproduces the monitored incident.", + "kind": "runnable", + "used_by": ["setup", "results"], + }, + ) + + catalog = build_topic_catalog(docs_dir, taxonomy) + topic = catalog.topics[("azure-monitor", "agent-topic")] + + assert topic.entry.relative_path == PurePosixPath( + "services/azure-monitor/agent-topic/index.md" + ) + assert [member.relative_path for member in topic.members] == [ + PurePosixPath("services/azure-monitor/agent-topic/index.md"), + PurePosixPath("services/azure-monitor/agent-topic/setup/index.md"), + PurePosixPath("services/azure-monitor/agent-topic/results/index.md"), + ] + assert catalog.position_by_document[topic.members[2].relative_path] == 2 + assert [ + sample.slug + for sample in catalog.samples_by_document[topic.members[1].relative_path] + ] == ["event-lab"] + + +def test_build_topic_catalog_discovers_only_canonical_documents( + tmp_path: Path, taxonomy: dict +) -> None: + docs_dir = tmp_path / "docs" + write_document( + docs_dir, + "services/azure-monitor/agent-topic/index.md", + "Agent topic", + ) + write_document( + docs_dir, + "services/azure-monitor/agent-topic/setup/index.md", + "Setup", + topic_order=1, + ) + write_document( + docs_dir, + "guides/azure-monitor/agent-topic/index.md", + "Legacy topic", + ) + write_sample( + docs_dir, + "services/azure-monitor/agent-topic/samples/entry-lab", + { + "title": "Entry lab", + "description": "Owned by the canonical entry only.", + "kind": "runnable", + "used_by": ["index"], + }, + ) + + catalog = build_topic_catalog(docs_dir, taxonomy) + + assert [sample.slug for sample in catalog.samples_by_document[PurePosixPath("services/azure-monitor/agent-topic/index.md")]] == [ + "entry-lab" + ] + assert all( + document.relative_path.parts[0] == "services" + for document in catalog.documents + ) + assert PurePosixPath( + "guides/azure-monitor/agent-topic/index.md" + ) not in catalog.by_document + + +@pytest.mark.parametrize("unrelated_topic", [False, True]) +def test_build_topic_catalog_rejects_samples_without_topic_entry( + tmp_path: Path, taxonomy: dict, unrelated_topic: bool +) -> None: + docs_dir = tmp_path / "docs" + if unrelated_topic: + write_document( + docs_dir, + "services/azure-monitor/other-topic/index.md", + "Other topic", + ) + write_sample( + docs_dir, + "services/azure-monitor/orphan-topic/samples/event-lab", + { + "title": "Event lab", + "description": "No topic owns this sample.", + "kind": "runnable", + "used_by": ["index"], + }, + ) + + with pytest.raises( + DocumentFormatError, + match=re.escape("services/azure-monitor/orphan-topic/index.md: topic entry document is missing"), + ): + build_topic_catalog(docs_dir, taxonomy) + + +@pytest.mark.parametrize("child_document", [False, True]) +def test_build_topic_catalog_rejects_samples_beneath_child( + tmp_path: Path, taxonomy: dict, child_document: bool +) -> None: + docs_dir = tmp_path / "docs" + write_document(docs_dir, "services/azure-monitor/agent-topic/index.md", "Agent topic") + if child_document: + write_document( + docs_dir, + "services/azure-monitor/agent-topic/setup/index.md", + "Setup", + topic_order=1, + ) + write_sample( + docs_dir, + "services/azure-monitor/agent-topic/setup/samples/event-lab", + { + "title": "Event lab", + "description": "Misplaced sample.", + "kind": "runnable", + "used_by": ["index"], + }, + ) + + with pytest.raises( + DocumentFormatError, + match=re.escape("services/azure-monitor/agent-topic/setup/samples: samples must be directly below the topic"), + ): + build_topic_catalog(docs_dir, taxonomy) + + +def test_build_topic_catalog_rejects_loose_files_in_topic_samples( + tmp_path: Path, taxonomy: dict +) -> None: + docs_dir = tmp_path / "docs" + write_document(docs_dir, "services/azure-monitor/agent-topic/index.md", "Agent topic") + samples_root = docs_dir / "services/azure-monitor/agent-topic/samples" + samples_root.mkdir() + (samples_root / "loose.py").write_text("print('unowned')\n", encoding="utf-8") + + with pytest.raises( + DocumentFormatError, + match=re.escape("services/azure-monitor/agent-topic/samples/loose.py: sample files must belong to a sample package"), + ): + build_topic_catalog(docs_dir, taxonomy) + + +@pytest.mark.parametrize("sample_slug", ["event-lab", "samples"]) +def test_build_topic_catalog_allows_samples_directories_inside_sample_payload( + tmp_path: Path, taxonomy: dict, sample_slug: str +) -> None: + docs_dir = tmp_path / "docs" + entry_path = "services/azure-monitor/agent-topic/index.md" + write_document(docs_dir, entry_path, "Agent topic") + sample_dir = write_sample( + docs_dir, + f"services/azure-monitor/agent-topic/samples/{sample_slug}", + { + "title": "Event lab", + "description": "Contains nested example payloads.", + "kind": "runnable", + "used_by": ["index"], + }, + ) + for relative in ("samples/demo", "src/samples/demo", "samples/demo/samples/inner"): + payload = sample_dir / relative + payload.mkdir(parents=True, exist_ok=True) + (payload / "index.md").write_text("# Payload, not a public document\n", encoding="utf-8") + (payload / "sample.yml").write_text("not a sample manifest\n", encoding="utf-8") + (sample_dir / "samples/loose.py").write_text("print('owned payload')\n", encoding="utf-8") + + catalog = build_topic_catalog(docs_dir, taxonomy) + + assert len(catalog.documents) == 1 + assert [sample.slug for sample in catalog.samples_by_document[PurePosixPath(entry_path)]] == [ + sample_slug + ] + + +@pytest.mark.parametrize( + ("invalid_field", "invalid_value", "expected"), + [ + ("sample.yml", None, "sample.yml is required"), + ("README.md", None, "README.md is required"), + ("kind", "unsupported", "kind must be runnable or artifact"), + ("used_by", ["missing"], "unknown document slug: missing"), + ("used_by", [], "used_by must be a non-empty list"), + ("used_by", ["index", "index"], "used_by values must be unique"), + ], +) +def test_build_topic_catalog_validates_every_immediate_sample_package( + tmp_path: Path, taxonomy: dict, invalid_field: str, invalid_value: object, expected: str +) -> None: + docs_dir = tmp_path / "docs" + write_document(docs_dir, "services/azure-monitor/agent-topic/index.md", "Agent topic") + manifest = { + "title": "Event lab", + "description": "Owned by the topic.", + "kind": "artifact", + "used_by": ["index"], + } + write_sample(docs_dir, "services/azure-monitor/agent-topic/samples/a-valid", manifest) + invalid_sample = write_sample( + docs_dir, "services/azure-monitor/agent-topic/samples/z-invalid", manifest + ) + if invalid_field in {"sample.yml", "README.md"}: + (invalid_sample / invalid_field).unlink() + else: + (invalid_sample / "sample.yml").write_text( + yaml.safe_dump({**manifest, invalid_field: invalid_value}), encoding="utf-8" + ) + + with pytest.raises(DocumentFormatError, match=re.escape(f"samples/z-invalid: {expected}")): + build_topic_catalog(docs_dir, taxonomy) + + +def test_build_topic_catalog_rejects_reserved_index_child_slug( + tmp_path: Path, taxonomy: dict +) -> None: + docs_dir = tmp_path / "docs" + write_document(docs_dir, "services/azure-monitor/agent-topic/index.md", "Agent topic") + write_document( + docs_dir, + "services/azure-monitor/agent-topic/index/index.md", + "Reserved child", + topic_order=1, + ) + + with pytest.raises( + DocumentFormatError, + match=re.escape("services/azure-monitor/agent-topic/index/index.md: child slug 'index' is reserved for the topic entry"), + ): + build_topic_catalog(docs_dir, taxonomy) + + +def test_reserved_child_cannot_redirect_entry_sample_ownership( + tmp_path: Path, taxonomy: dict +) -> None: + docs_dir = tmp_path / "docs" + entry_path = PurePosixPath("services/azure-monitor/agent-topic/index.md") + write_document(docs_dir, entry_path.as_posix(), "Agent topic") + write_sample( + docs_dir, + "services/azure-monitor/agent-topic/samples/entry-lab", + { + "title": "Entry lab", + "description": "Owned only by the topic entry.", + "kind": "runnable", + "used_by": ["index"], + }, + ) + catalog = build_topic_catalog(docs_dir, taxonomy) + assert catalog.samples_by_document[entry_path][0].used_by == (entry_path,) + write_document( + docs_dir, + "services/azure-monitor/agent-topic/index/index.md", + "Cannot take entry ownership", + topic_order=1, + ) + + with pytest.raises( + DocumentFormatError, match="child slug 'index' is reserved for the topic entry" + ): + build_topic_catalog(docs_dir, taxonomy) + + +@pytest.mark.parametrize( + ("case", "expected"), + [ + ("missing root index", "topic entry document is missing"), + ("child without topic_order", "topic_order must be a positive integer"), + ("duplicate child order", "duplicate topic_order 1"), + ("non-contiguous order", "topic_order values must be contiguous from 1"), + ("nested grandchild", "child documents must be directly below the topic"), + ("sample missing manifest", "sample.yml is required"), + ("sample missing readme", "README.md is required"), + ("invalid sample kind", "kind must be runnable or artifact"), + ("empty used_by", "used_by must be a non-empty list"), + ("unknown used_by", "unknown document slug"), + ("duplicate redirect", "redirect_from path is already used"), + ], +) +def test_build_topic_catalog_rejects_invalid_topic_and_sample_layouts( + tmp_path: Path, taxonomy: dict, case: str, expected: str +) -> None: + docs_dir = tmp_path / "docs" + write_document( + docs_dir, + "services/azure-monitor/agent-topic/index.md", + "Agent topic", + ) + write_document( + docs_dir, + "services/azure-monitor/agent-topic/setup/index.md", + "Setup", + topic_order=1, + ) + + if case == "missing root index": + (docs_dir / "services/azure-monitor/agent-topic/index.md").unlink() + elif case == "child without topic_order": + write_document( + docs_dir, + "services/azure-monitor/agent-topic/results/index.md", + "Results", + ) + elif case == "duplicate child order": + write_document( + docs_dir, + "services/azure-monitor/agent-topic/results/index.md", + "Results", + topic_order=1, + ) + elif case == "non-contiguous order": + write_document( + docs_dir, + "services/azure-monitor/agent-topic/results/index.md", + "Results", + topic_order=3, + ) + elif case == "nested grandchild": + write_document( + docs_dir, + "services/azure-monitor/agent-topic/setup/deeper/index.md", + "Deeper", + topic_order=2, + ) + elif case == "sample missing manifest": + write_sample( + docs_dir, + "services/azure-monitor/agent-topic/samples/event-lab", + None, + ) + elif case == "sample missing readme": + write_sample( + docs_dir, + "services/azure-monitor/agent-topic/samples/event-lab", + { + "title": "Event lab", + "description": "Reproduces the monitored incident.", + "kind": "runnable", + "used_by": ["setup"], + }, + readme=False, + ) + elif case == "invalid sample kind": + write_sample( + docs_dir, + "services/azure-monitor/agent-topic/samples/event-lab", + { + "title": "Event lab", + "description": "Reproduces the monitored incident.", + "kind": "broken", + "used_by": ["setup"], + }, + ) + elif case == "empty used_by": + write_sample( + docs_dir, + "services/azure-monitor/agent-topic/samples/event-lab", + { + "title": "Event lab", + "description": "Reproduces the monitored incident.", + "kind": "runnable", + "used_by": [], + }, + ) + elif case == "unknown used_by": + write_sample( + docs_dir, + "services/azure-monitor/agent-topic/samples/event-lab", + { + "title": "Event lab", + "description": "Reproduces the monitored incident.", + "kind": "runnable", + "used_by": ["unknown"], + }, + ) + elif case == "duplicate redirect": + write_document( + docs_dir, + "services/azure-monitor/agent-topic/results/index.md", + "Results", + topic_order=2, + redirect_from=["guides/azure-monitor/shared/index.md"], + ) + write_document( + docs_dir, + "services/azure-monitor/agent-topic/verify/index.md", + "Verify", + topic_order=3, + redirect_from=["guides/azure-monitor/shared/index.md"], + ) + with pytest.raises(DocumentFormatError, match=re.escape(expected)): + build_topic_catalog(docs_dir, taxonomy) + + +def test_validate_document_rejects_canonical_service_paths_absent_from_taxonomy( + tmp_path: Path, taxonomy: dict +) -> None: + path = write_document( + tmp_path / "docs", + "services/unknown-service/agent-topic/index.md", + "Agent topic", + services=["unknown-service"], + ) + from scripts.docs.content import load_document + + document = load_document(path, docs_dir=tmp_path / "docs") + + errors = validate_document(document, taxonomy) + + assert "unknown service: unknown-service" in errors + + +def test_build_topic_catalog_ignores_legacy_collection_documents( + tmp_path: Path, taxonomy: dict +) -> None: + docs_dir = tmp_path / "docs" + write_document( + docs_dir, + "guides/azure-monitor/agent-topic/index.md", + "Guide legacy topic", + ) + write_document( + docs_dir, + "labs/azure-monitor/agent-topic/index.md", + "Lab legacy topic", + ) + + catalog = build_topic_catalog(docs_dir, taxonomy) + + assert catalog.documents == () + assert catalog.topics == {} + + +def test_repository_connected_topics_have_canonical_layout() -> None: + expected = { + ("microsoft-foundry", "agent-memory"): { + "slugs": [ + "index", + "taxonomy", + "architecture-patterns", + "pipeline-retrieval", + "frameworks", + "production-evaluation", + "commerce", + ], + "redirects": [ + "research/microsoft-foundry/agent-memory-overview/index.md", + "research/microsoft-foundry/agent-memory-taxonomy/index.md", + "research/microsoft-foundry/agent-memory-architecture-patterns/index.md", + "research/microsoft-foundry/agent-memory-pipeline-retrieval/index.md", + "research/microsoft-foundry/agent-memory-frameworks/index.md", + "research/microsoft-foundry/agent-memory-production-evaluation/index.md", + "research/microsoft-foundry/agent-memory-commerce/index.md", + ], + }, + ("azure-monitor", "azure-sre-agent"): { + "slugs": [ + "index", + "event-lab", + "setup", + "scenario-http-500", + "scenario-latency", + "scenario-blob-permission", + "results", + "validation-results", + "incident-runbook", + "dynamic-thresholds", + ], + "redirects": [ + "guides/azure-monitor/azure-sre-agent-overview/index.md", + "labs/azure-monitor/sre-agent-event-lab/index.md", + "labs/azure-monitor/sre-agent-event-lab-setup/index.md", + "labs/azure-monitor/sre-agent-scenario-http-500/index.md", + "labs/azure-monitor/sre-agent-scenario-latency/index.md", + "labs/azure-monitor/sre-agent-scenario-blob-permission/index.md", + "labs/azure-monitor/sre-agent-results/index.md", + "research/azure-monitor/sre-agent-validation-results/index.md", + "guides/azure-monitor/sre-agent-incident-runbook/index.md", + "guides/azure-monitor/sre-agent-dynamic-thresholds/index.md", + ], + }, + ("azure-ai-search", "custom-vectorization"): { + "slugs": [ + "index", + "rag-chunking", + "custom-web-api", + "gpu-vllm", + "bge-m3-vs-qwen3", + "a10-vs-t4", + ], + "redirects": [ + "guides/azure-ai-search/custom-embedding-ingestion/index.md", + "research/azure-ai-search/rag-chunking-strategies/index.md", + "guides/azure-ai-search/custom-web-api-vectorization/index.md", + "guides/azure-ai-search/gpu-vllm-rag/index.md", + "research/azure-ai-search/bge-m3-vs-qwen3-embedding/index.md", + "research/azure-ai-search/a10-vs-t4-embedding-benchmark/index.md", + ], + }, + ("azure-monitor", "hdinsight-kafka-monitoring"): { + "slugs": ["index", "prometheus-grafana", "catch-up-benchmark"], + "redirects": [ + "research/azure-monitor/hdinsight-kafka-monitoring-options/index.md", + "guides/azure-monitor/hdinsight-kafka-prometheus-grafana/index.md", + "research/azure-hdinsight/kafka-catchup-sku-fetch-benchmark/index.md", + ], + }, + } + catalog = build_topic_catalog( + ROOT / "docs", load_taxonomy(ROOT / "docs-taxonomy.yml") + ) + + for topic_key, topic_expected in expected.items(): + topic = catalog.topics[topic_key] + slugs = [ + "index" if member == topic.entry else member.relative_path.parts[-2] + for member in topic.members + ] + + assert slugs == topic_expected["slugs"] + assert "topic_order" not in topic.entry.metadata + assert [ + member.metadata.get("redirect_from") for member in topic.members + ] == [[redirect] for redirect in topic_expected["redirects"]] + + +def test_repository_uses_only_canonical_topic_packages() -> None: + moved_documents = { + ("application-development", "nodejs-file-io-cpu"): + "guides/application-development/nodejs-file-io-cpu/index.md", + ("azure-ai-search", "eventual-consistency-reindex"): + "cases/azure-ai-search/eventual-consistency-reindex/index.md", + ("azure-ai-search", "korean-analyzer-comparison"): + "guides/azure-ai-search/korean-analyzer-comparison/index.md", + ("azure-application-gateway", "sse-response-buffering"): + "guides/azure-application-gateway/sse-response-buffering/index.md", + ("azure-application-gateway", "waf-path-ip-allowlist"): + "guides/azure-application-gateway/waf-path-ip-allowlist/index.md", + ("azure-architecture", "response-time-optimization"): + "guides/azure-architecture/response-time-optimization/index.md", + ("azure-automation", "portal-cli-limitations"): + "guides/azure-automation/portal-cli-limitations/index.md", + ("azure-cosmos-db", "nodejs-client-optimization"): + "guides/azure-cosmos-db/nodejs-client-optimization/index.md", + ("azure-cosmos-db", "nodejs-dns-lookup-bottleneck"): + "guides/azure-cosmos-db/nodejs-dns-lookup-bottleneck/index.md", + ("azure-cosmos-db", "point-read-optimization"): + "guides/azure-cosmos-db/point-read-optimization/index.md", + ("azure-database-for-mysql", "blue-green-upgrade"): + "guides/azure-database-for-mysql/blue-green-upgrade/index.md", + ("azure-database-for-mysql", "nodejs-read-write-routing"): + "guides/azure-database-for-mysql/nodejs-read-write-routing/index.md", + ("azure-kubernetes-service", "file-io-throttling"): + "cases/azure-kubernetes-service/file-io-throttling/index.md", + ("azure-kubernetes-service", "netapp-files-cpu-io-wait"): + "cases/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md", + ("azure-kubernetes-service", "pod-database-query-latency"): + "cases/azure-kubernetes-service/pod-database-query-latency/index.md", + ("azure-kubernetes-service", "argocd-image-updater-acr"): + "guides/azure-kubernetes-service/argocd-image-updater-acr/index.md", + ("azure-kubernetes-service", "authorization-troubleshooting"): + "guides/azure-kubernetes-service/authorization-troubleshooting/index.md", + ("azure-kubernetes-service", "cni-overlay-nsg"): + "guides/azure-kubernetes-service/cni-overlay-nsg/index.md", + ("azure-kubernetes-service", "kaito-open-source-model"): + "guides/azure-kubernetes-service/kaito-open-source-model/index.md", + ("azure-kubernetes-service", "pod-affinity-distribution"): + "guides/azure-kubernetes-service/pod-affinity-distribution/index.md", + ("azure-kubernetes-service", "pod-scheduling-agent-pools"): + "guides/azure-kubernetes-service/pod-scheduling-agent-pools/index.md", + ("azure-kubernetes-service", "pyroscope-anf-s3"): + "guides/azure-kubernetes-service/pyroscope-anf-s3/index.md", + ("azure-kubernetes-service", "python-memory-leak-memray"): + "guides/azure-kubernetes-service/python-memory-leak-memray/index.md", + ("azure-kubernetes-service", "remote-cluster-local-development"): + "guides/azure-kubernetes-service/remote-cluster-local-development/index.md", + ("azure-kubernetes-service", "spot-h100-kaito"): + "guides/azure-kubernetes-service/spot-h100-kaito/index.md", + ("azure-kubernetes-service", "workload-identity-databricks"): + "guides/azure-kubernetes-service/workload-identity-databricks/index.md", + ("azure-load-testing", "locust-appgw-aks-private"): + "guides/azure-load-testing/locust-appgw-aks-private/index.md", + ("azure-managed-redis", "cluster-failover-recovery"): + "cases/azure-managed-redis/cluster-failover-recovery/index.md", + ("azure-monitor", "aks-private-opentelemetry"): + "guides/azure-monitor/aks-private-opentelemetry/index.md", + ("azure-monitor", "dynamic-thresholds-brief"): + "research/azure-monitor/dynamic-thresholds-brief/index.md", + ("azure-openai", "adaptive-ptu-load-balancing"): + "guides/azure-openai/adaptive-ptu-load-balancing/index.md", + ("azure-storage", "mobile-resumable-upload-tus"): + "guides/azure-storage/mobile-resumable-upload-tus/index.md", + ("microsoft-foundry", "agent-framework-2026"): + "research/microsoft-foundry/agent-framework-2026/index.md", + ("microsoft-foundry", "codex-closed-network"): + "guides/microsoft-foundry/codex-closed-network/index.md", + ("microsoft-foundry", "foundry-local-air-gapped"): + "guides/microsoft-foundry/foundry-local-air-gapped/index.md", + ("microsoft-foundry", "gpt-memory-layer"): + "guides/microsoft-foundry/gpt-memory-layer/index.md", + } + catalog = build_topic_catalog( + ROOT / "docs", load_taxonomy(ROOT / "docs-taxonomy.yml") + ) + + assert len(catalog.documents) == 62 + assert len(catalog.topics) == 40 + assert not any( + (ROOT / "docs" / name).exists() + for name in ("cases", "guides", "labs", "research") + ) + assert not (ROOT / "samples").exists() + assert all( + document.relative_path.parts[0] == "services" + for document in catalog.documents + ) + assert { + redirect + for topic_key, redirect in moved_documents.items() + if catalog.topics[topic_key].entry.metadata.get("redirect_from") == [redirect] + } == set(moved_documents.values()) + + +def test_mobile_upload_commands_use_the_canonical_sample_path() -> None: + guide = ( + ROOT + / "docs" + / "services" + / "azure-storage" + / "mobile-resumable-upload-tus" + / "index.md" + ).read_text(encoding="utf-8") + sample_path = ( + "docs/services/azure-storage/mobile-resumable-upload-tus/" + "samples/spring-application" + ) + + assert f"cd {sample_path}\n" in guide + assert f"cd {sample_path}/scripts\n" in guide + assert "cd azureblob/spring-resumable-upload" not in guide + assert "cd spring-resumable-upload/scripts" not in guide diff --git a/tests/docs/test_validators.py b/tests/docs/test_validators.py index 2d8f59b..ada2148 100644 --- a/tests/docs/test_validators.py +++ b/tests/docs/test_validators.py @@ -34,6 +34,31 @@ """ +def canonical_guide(title: str, *, topic_order: int | None = None) -> str: + topic_order_line = f"topic_order: {topic_order}\n" if topic_order is not None else "" + return f"""\ +--- +title: {title} +description: {title} 설명 +document_type: guide +services: [aks] +technologies: [kubernetes] +tags: [networking] +status: current +verification_status: verified +sources_checked_at: 2026-09-12 +official_sources: + - title: Azure Kubernetes Service documentation + url: https://learn.microsoft.com/azure/aks/ +last_verified: 2026-09-12 +review_cycle_days: 180 +applies_to: [AKS 1.34+] +{topic_order_line}--- + +# {title} +""" + + def make_repository(tmp_path: Path) -> Path: taxonomy = { "collections": { @@ -70,7 +95,7 @@ def make_repository(tmp_path: Path) -> Path: ), encoding="utf-8", ) - page = tmp_path / "docs" / "guides" / "aks" / "network-diagnosis" + page = tmp_path / "docs" / "services" / "aks" / "network-diagnosis" (page / "images").mkdir(parents=True) (page / "index.md").write_text(VALID_GUIDE, encoding="utf-8") (page / "images" / "network.png").write_bytes(b"png") @@ -90,26 +115,52 @@ def test_validation_gates_accept_a_publishable_repository(tmp_path: Path) -> Non assert all(result.errors == [] for result in results) -def test_metadata_gate_checks_markdown_below_each_collection(tmp_path: Path) -> None: +def test_metadata_gate_rejects_non_bundle_markdown_below_services(tmp_path: Path) -> None: root = make_repository(tmp_path) - stray = root / "docs" / "guides" / "aks" / "network-diagnosis" / "topic.md" + stray = root / "docs" / "services" / "aks" / "network-diagnosis" / "topic.md" stray.write_text(VALID_GUIDE, encoding="utf-8") result = validate_metadata.validate_repository(root, today=date(2026, 9, 12)) assert result.document_count == 2 assert any( - "public documents must use ///index.md" in error + "public documents must use services//[/]/index.md" + in error and "topic.md" in error for error in result.errors ) +def test_validation_gates_discover_only_canonical_documents_and_reject_legacy_sources( + tmp_path: Path, +) -> None: + root = make_repository(tmp_path) + legacy = root / "docs" / "guides" / "aks" / "legacy-topic" / "index.md" + legacy.parent.mkdir(parents=True) + legacy.write_text(VALID_GUIDE, encoding="utf-8") + + metadata = validate_metadata.validate_repository(root, today=date(2026, 9, 12)) + sources = validate_sources.validate_repository(root, today=date(2026, 9, 12)) + links = validate_links.validate_repository(root) + + assert metadata.document_count == 2 + assert any( + "docs/guides/aks/legacy-topic/index.md: " + "public documents must use services//[/]/index.md" + in error + for error in metadata.errors + ) + assert sources.document_count == 1 + assert sources.errors == [] + assert links.document_count == 1 + assert links.errors == [] + + def test_source_and_link_gates_reject_an_unpublishable_page( tmp_path: Path, ) -> None: root = make_repository(tmp_path) - page = root / "docs" / "guides" / "aks" / "network-diagnosis" / "index.md" + page = root / "docs" / "services" / "aks" / "network-diagnosis" / "index.md" text = ( VALID_GUIDE.replace("2026-09-12", "2026-09-13", 1) .replace( @@ -148,7 +199,7 @@ def test_reference_links_and_images_are_validated( tmp_path: Path, snippet: str, expected: str ) -> None: root = make_repository(tmp_path) - page = root / "docs" / "guides" / "aks" / "network-diagnosis" / "index.md" + page = root / "docs" / "services" / "aks" / "network-diagnosis" / "index.md" page.write_text(VALID_GUIDE + "\n" + snippet + "\n", encoding="utf-8") result = validate_links.validate_repository(root) @@ -159,7 +210,7 @@ def test_reference_links_and_images_are_validated( def test_reference_links_accept_existing_targets(tmp_path: Path) -> None: root = make_repository(tmp_path) - page = root / "docs" / "guides" / "aks" / "network-diagnosis" / "index.md" + page = root / "docs" / "services" / "aks" / "network-diagnosis" / "index.md" page.write_text( VALID_GUIDE + '\n![diagram][ASSET]\n\n[asset]: "Diagram"\n' @@ -185,7 +236,7 @@ def test_link_examples_are_ignored_without_hiding_following_links( tmp_path: Path, snippet: str ) -> None: root = make_repository(tmp_path) - page = root / "docs" / "guides" / "aks" / "network-diagnosis" / "index.md" + page = root / "docs" / "services" / "aks" / "network-diagnosis" / "index.md" body = VALID_GUIDE + "\n" + snippet + "\n" page.write_text(body, encoding="utf-8") @@ -200,10 +251,101 @@ def test_link_examples_are_ignored_without_hiding_following_links( def test_link_gate_reports_invalid_front_matter(tmp_path: Path) -> None: root = make_repository(tmp_path) - page = root / "docs" / "guides" / "aks" / "network-diagnosis" / "index.md" + page = root / "docs" / "services" / "aks" / "network-diagnosis" / "index.md" page.write_text("# Missing front matter\n", encoding="utf-8") result = validate_links.validate_repository(root) assert result.document_count == 1 assert any("YAML front matter" in error for error in result.errors) + + +def test_link_gate_validates_mixed_layout_pages_without_topic_samples( + tmp_path: Path, +) -> None: + root = make_repository(tmp_path) + topic = root / "docs" / "services" / "aks" / "network-diagnosis" + (topic / "index.md").write_text( + canonical_guide("Overview"), + encoding="utf-8", + ) + (topic / "setup").mkdir() + (topic / "setup" / "index.md").write_text( + canonical_guide("Setup", topic_order=1) + + "\n[broken canonical link](missing-child.md)\n", + encoding="utf-8", + ) + sample = topic / "samples" / "example" + sample.mkdir(parents=True) + (sample / "index.md").write_text( + canonical_guide("Sample") + "\n[ignored sample link](missing-sample.md)\n", + encoding="utf-8", + ) + + result = validate_links.validate_repository(root) + + assert result.document_count == 2 + assert result.errors == [ + "docs/services/aks/network-diagnosis/setup/index.md: " + "target does not exist: missing-child.md" + ] + + +def test_metadata_gate_reports_topic_catalog_errors_once_per_path(tmp_path: Path) -> None: + root = make_repository(tmp_path) + topic = root / "docs" / "services" / "aks" / "network-diagnosis" + (topic / "index.md").write_text( + canonical_guide("Overview"), + encoding="utf-8", + ) + (topic / "setup" / "index.md").parent.mkdir(parents=True) + (topic / "setup" / "index.md").write_text( + canonical_guide("Setup"), + encoding="utf-8", + ) + + result = validate_metadata.validate_repository(root, today=date(2026, 9, 12)) + + matching = [ + error + for error in result.errors + if "services/aks/network-diagnosis/setup/index.md" in error + ] + assert len(matching) == 1 + assert "topic_order must be a positive integer" in matching[0] + + +def test_metadata_gate_keeps_topic_validation_when_another_document_has_invalid_yaml( + tmp_path: Path, +) -> None: + root = make_repository(tmp_path) + bad = root / "docs" / "services" / "aks" / "broken" / "index.md" + bad.parent.mkdir(parents=True) + bad.write_text("---\ntitle: [broken\n---\n", encoding="utf-8") + topic = root / "docs" / "services" / "aks" / "network-diagnosis" + topic = root / "docs" / "services" / "aks" / "network-diagnosis" + (topic / "index.md").write_text(canonical_guide("Overview"), encoding="utf-8") + (topic / "setup").mkdir() + (topic / "setup" / "index.md").write_text( + canonical_guide("Setup"), + encoding="utf-8", + ) + (topic / "samples" / "broken-sample").mkdir(parents=True) + (topic / "samples" / "broken-sample" / "sample.yml").write_text( + "title: Broken sample\ndescription: Missing readme\nkind: runnable\nused_by: [setup]\n", + encoding="utf-8", + ) + + result = validate_metadata.validate_repository(root, today=date(2026, 9, 12)) + + assert any("broken/index.md: invalid YAML front matter" in error for error in result.errors) + assert any( + "services/aks/network-diagnosis/setup/index.md: topic_order must be a positive integer" + in error + for error in result.errors + ) + assert any( + "services/aks/network-diagnosis/samples/broken-sample: README.md is required" + in error + for error in result.errors + ) diff --git a/samples/azure-app-service/oryx-test/app.py b/tests/fixtures/oryx-python-app/app.py similarity index 100% rename from samples/azure-app-service/oryx-test/app.py rename to tests/fixtures/oryx-python-app/app.py diff --git a/samples/azure-app-service/oryx-test/requirements.txt b/tests/fixtures/oryx-python-app/requirements.txt similarity index 100% rename from samples/azure-app-service/oryx-test/requirements.txt rename to tests/fixtures/oryx-python-app/requirements.txt