From 4a34c312abad4e02fa481e2e1e07172f4f8ad688 Mon Sep 17 00:00:00 2001 From: hellices Date: Sun, 13 Sep 2026 23:09:55 +0900 Subject: [PATCH 01/26] ci: upgrade GitHub Actions to Node 24 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/docs-ci.yml | 6 ++- .github/workflows/oryx-python-build-test.yml | 2 +- .github/workflows/pages.yml | 12 +++--- tests/docs/test_pages_artifact.py | 4 +- tests/docs/test_workflows.py | 40 ++++++++++++++++++++ 5 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 tests/docs/test_workflows.py diff --git a/.github/workflows/docs-ci.yml b/.github/workflows/docs-ci.yml index e96f8def..131b2c88 100644 --- a/.github/workflows/docs-ci.yml +++ b/.github/workflows/docs-ci.yml @@ -13,10 +13,12 @@ jobs: timeout-minutes: 15 steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 + with: + fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: "3.13" cache: pip diff --git a/.github/workflows/oryx-python-build-test.yml b/.github/workflows/oryx-python-build-test.yml index f1a7b7b5..aac6d7f9 100644 --- a/.github/workflows/oryx-python-build-test.yml +++ b/.github/workflows/oryx-python-build-test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Pull Oryx build image run: docker pull mcr.microsoft.com/oryx/build:github-actions-debian-bullseye diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 9942f783..b2dc0953 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -22,10 +22,12 @@ jobs: timeout-minutes: 15 steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 + with: + fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: "3.13" cache: pip @@ -48,10 +50,10 @@ jobs: run: python scripts/docs/validate_search_index.py - name: Configure Pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@v6 - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v4 + uses: actions/upload-pages-artifact@v5 with: path: site @@ -68,4 +70,4 @@ jobs: steps: - name: Deploy to Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/tests/docs/test_pages_artifact.py b/tests/docs/test_pages_artifact.py index 3faecb52..c8d60075 100644 --- a/tests/docs/test_pages_artifact.py +++ b/tests/docs/test_pages_artifact.py @@ -13,7 +13,7 @@ def pages_artifact_filter(member: tarfile.TarInfo) -> tarfile.TarInfo | None: - # GNU tar's --exclude=".[^/]*" in upload-pages-artifact@v4, at every depth. + # GNU tar's --exclude=".[^/]*" in upload-pages-artifact@v5, at every depth. # Checking components also works on hosts whose BSD tar glob rules differ. if any(component.startswith(".") for component in PurePosixPath(member.name).parts): return None @@ -23,7 +23,7 @@ def pages_artifact_filter(member: tarfile.TarInfo) -> tarfile.TarInfo | None: def test_current_published_assets_survive_pages_artifact_hidden_exclusion(tmp_path: Path) -> None: workflow = yaml.safe_load((ROOT / ".github/workflows/pages.yml").read_text()) assert any( - step.get("uses") == "actions/upload-pages-artifact@v4" + step.get("uses") == "actions/upload-pages-artifact@v5" for step in workflow["jobs"]["build"]["steps"] ) catalog = build_topic_catalog(ROOT / "docs", load_taxonomy(ROOT / "docs-taxonomy.yml")) diff --git a/tests/docs/test_workflows.py b/tests/docs/test_workflows.py new file mode 100644 index 00000000..342bc81a --- /dev/null +++ b/tests/docs/test_workflows.py @@ -0,0 +1,40 @@ +from pathlib import Path +import re + + +ROOT = Path(__file__).parents[2] +WORKFLOWS = ROOT / ".github" / "workflows" +REQUIRED_ACTIONS = { + "actions/checkout": "v7", + "actions/setup-python": "v7", + "actions/configure-pages": "v6", + "actions/upload-pages-artifact": "v5", + "actions/deploy-pages": "v5", +} + + +def action_references() -> list[tuple[Path, str, str]]: + references = [] + for path in sorted(WORKFLOWS.glob("*.yml")): + for owner_repo, version in re.findall( + r"^\s*uses:\s*([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+)@([^\s#]+)", + path.read_text(encoding="utf-8"), + flags=re.MULTILINE, + ): + references.append((path, owner_repo, version)) + return references + + +def test_official_actions_use_required_node_24_majors() -> None: + references = action_references() + for path, action, version in references: + if action in REQUIRED_ACTIONS: + assert version == REQUIRED_ACTIONS[action], (path, action, version) + assert set(REQUIRED_ACTIONS) <= {action for _, action, _ in references} + + +def test_historical_audit_workflows_checkout_full_history() -> None: + for name in ("docs-ci.yml", "pages.yml"): + text = (WORKFLOWS / name).read_text(encoding="utf-8") + checkout = text.split("actions/checkout@v7", 1)[1].split("\n\n", 1)[0] + assert "fetch-depth: 0" in checkout From 4853f87990d5a17f02ba2265753592b9c7068a43 Mon Sep 17 00:00:00 2001 From: hellices Date: Sun, 13 Sep 2026 23:14:27 +0900 Subject: [PATCH 02/26] test: harden workflow contract checks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/docs/test_workflows.py | 183 ++++++++++++++++++++++++++++++++--- 1 file changed, 169 insertions(+), 14 deletions(-) diff --git a/tests/docs/test_workflows.py b/tests/docs/test_workflows.py index 342bc81a..aa9c3133 100644 --- a/tests/docs/test_workflows.py +++ b/tests/docs/test_workflows.py @@ -1,5 +1,8 @@ +from collections.abc import Iterator from pathlib import Path -import re + +import pytest +import yaml ROOT = Path(__file__).parents[2] @@ -13,28 +16,180 @@ } -def action_references() -> list[tuple[Path, str, str]]: +def write_workflow(path: Path, body: str) -> None: + path.write_text(body, encoding="utf-8") + + +def iter_job_steps(node: object) -> Iterator[dict[str, object]]: + if isinstance(node, dict): + steps = node.get("steps") + if isinstance(steps, list): + for step in steps: + if isinstance(step, dict): + yield step + for value in node.values(): + yield from iter_job_steps(value) + elif isinstance(node, list): + for item in node: + yield from iter_job_steps(item) + + +def workflow_steps(workflows_dir: Path = WORKFLOWS) -> list[tuple[Path, dict[str, object]]]: + steps = [] + for path in sorted(workflows_dir.glob("*.yml")): + workflow = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + if not isinstance(workflow, dict): + continue + jobs = workflow.get("jobs") + if not isinstance(jobs, dict): + continue + for job in jobs.values(): + for step in iter_job_steps(job): + steps.append((path, step)) + return steps + + +def action_references(workflows_dir: Path = WORKFLOWS) -> list[tuple[Path, str, str]]: references = [] - for path in sorted(WORKFLOWS.glob("*.yml")): - for owner_repo, version in re.findall( - r"^\s*uses:\s*([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+)@([^\s#]+)", - path.read_text(encoding="utf-8"), - flags=re.MULTILINE, - ): + for path, step in workflow_steps(workflows_dir): + uses = step.get("uses") + if isinstance(uses, str) and "@" in uses: + owner_repo, version = uses.rsplit("@", 1) references.append((path, owner_repo, version)) return references -def test_official_actions_use_required_node_24_majors() -> None: - references = action_references() +def checkout_inputs_by_workflow( + workflows_dir: Path = WORKFLOWS, +) -> dict[str, list[dict[str, object] | None]]: + checkout_inputs: dict[str, list[dict[str, object] | None]] = {} + for path, step in workflow_steps(workflows_dir): + if step.get("uses") != "actions/checkout@v7": + continue + with_block = step.get("with") + parsed_with = with_block if isinstance(with_block, dict) else None + checkout_inputs.setdefault(path.name, []).append(parsed_with) + return checkout_inputs + + +def assert_required_action_versions(workflows_dir: Path = WORKFLOWS) -> None: + references = action_references(workflows_dir) for path, action, version in references: if action in REQUIRED_ACTIONS: assert version == REQUIRED_ACTIONS[action], (path, action, version) assert set(REQUIRED_ACTIONS) <= {action for _, action, _ in references} -def test_historical_audit_workflows_checkout_full_history() -> None: +def assert_checkout_history_contract(workflows_dir: Path = WORKFLOWS) -> None: + checkout_inputs = checkout_inputs_by_workflow(workflows_dir) + assert checkout_inputs["docs-ci.yml"] == [{"fetch-depth": 0}] + assert checkout_inputs["pages.yml"] == [{"fetch-depth": 0}] + assert checkout_inputs["oryx-python-build-test.yml"] == [None] + + +def test_quoted_uses_values_are_detected_and_rejected(tmp_path: Path) -> None: + workflow = tmp_path / "quoted.yml" + write_workflow( + workflow, + """\ +name: Quoted action references +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: "actions/checkout@v4" + - uses: actions/setup-python@v7 + - uses: actions/configure-pages@v6 + - uses: actions/upload-pages-artifact@v5 + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/deploy-pages@v5 +""", + ) + + assert (workflow, "actions/checkout", "v4") in action_references(tmp_path) + with pytest.raises(AssertionError, match="actions/checkout"): + assert_required_action_versions(tmp_path) + + +def test_checkout_history_contract_rejects_extra_checkout_inputs(tmp_path: Path) -> None: + write_workflow( + tmp_path / "docs-ci.yml", + """\ +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + persist-credentials: false +""", + ) + write_workflow( + tmp_path / "pages.yml", + """\ +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 +""", + ) + write_workflow( + tmp_path / "oryx-python-build-test.yml", + """\ +jobs: + oryx-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 +""", + ) + + with pytest.raises(AssertionError): + assert_checkout_history_contract(tmp_path) + + +def test_checkout_history_contract_rejects_oryx_fetch_depth_override( + tmp_path: Path, +) -> None: for name in ("docs-ci.yml", "pages.yml"): - text = (WORKFLOWS / name).read_text(encoding="utf-8") - checkout = text.split("actions/checkout@v7", 1)[1].split("\n\n", 1)[0] - assert "fetch-depth: 0" in checkout + write_workflow( + tmp_path / name, + """\ +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 +""", + ) + write_workflow( + tmp_path / "oryx-python-build-test.yml", + """\ +jobs: + oryx-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 1 +""", + ) + + with pytest.raises(AssertionError): + assert_checkout_history_contract(tmp_path) + + +def test_official_actions_use_required_node_24_majors() -> None: + assert_required_action_versions() + + +def test_historical_audit_workflows_checkout_full_history() -> None: + assert_checkout_history_contract() From 75e44e935db98dcae28bf54ac58036375a7236c5 Mon Sep 17 00:00:00 2001 From: hellices Date: Sun, 13 Sep 2026 23:24:59 +0900 Subject: [PATCH 03/26] test(docs): inventory pre-Pages content lineage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/docs/pre_pages.py | 296 +++++++++++++ scripts/docs/pre_pages_inventory.yml | 223 ++++++++++ tests/docs/test_pre_pages.py | 628 +++++++++++++++++++++++++++ 3 files changed, 1147 insertions(+) create mode 100644 scripts/docs/pre_pages.py create mode 100644 scripts/docs/pre_pages_inventory.yml create mode 100644 tests/docs/test_pre_pages.py diff --git a/scripts/docs/pre_pages.py b/scripts/docs/pre_pages.py new file mode 100644 index 00000000..1dfdc9a3 --- /dev/null +++ b/scripts/docs/pre_pages.py @@ -0,0 +1,296 @@ +"""Fixed pre-Pages inventory and Git lineage (not a content-preservation audit).""" + +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path, PurePosixPath, PureWindowsPath +import re +import subprocess +from types import MappingProxyType +from typing import Mapping + +import yaml + +from scripts.docs.content import Document, DocumentFormatError, load_taxonomy +from scripts.docs.topics import build_topic_catalog + + +class AuditFormatError(ValueError): + """Raised when the inventory or its Git lineage cannot be validated.""" + + +@dataclass(frozen=True) +class BaselineDocument: + baseline_path: PurePosixPath + pages_path: PurePosixPath + reviewed_changes: Mapping[str, Mapping[str, str]] + + def __post_init__(self) -> None: + object.__setattr__( + self, + "reviewed_changes", + MappingProxyType({ + category: MappingProxyType(dict(reasons)) + for category, reasons in self.reviewed_changes.items() + }), + ) + + +@dataclass(frozen=True) +class ReviewedDisposition: + status: str + current_paths: tuple[PurePosixPath, ...] + reason: str + + +@dataclass(frozen=True) +class PrePagesInventory: + baseline_commit: str + pages_commit: str + rename_similarity: int + documents: tuple[BaselineDocument, ...] + dispositions: Mapping[PurePosixPath, ReviewedDisposition] + + def __post_init__(self) -> None: + object.__setattr__(self, "dispositions", MappingProxyType(dict(self.dispositions))) + + +@dataclass(frozen=True) +class GitFileDisposition: + baseline_path: PurePosixPath + status: str + current_paths: tuple[PurePosixPath, ...] + + +def _fields(value: object, required: set[str], label: str, optional: set[str] = frozenset()) -> dict: + if not isinstance(value, dict): + raise AuditFormatError(f"{label} must be a mapping") + missing = required - value.keys() + extra = value.keys() - required - optional + if missing or extra: + raise AuditFormatError( + f"{label}: missing fields {sorted(missing)}; unexpected fields {sorted(extra, key=str)}" + ) + return value + + +def _nonempty_string(value: object, label: str) -> str: + if not isinstance(value, str) or not value.strip(): + raise AuditFormatError(f"{label} must be a non-empty string") + return value + + +def _commit_hash(value: object, label: str) -> str: + if not isinstance(value, str) or not re.fullmatch(r"[0-9a-f]{40}", value): + raise AuditFormatError(f"{label} must be a full lowercase 40-character commit hash: {value!r}") + return value + + +def _relative_path(value: object, label: str) -> PurePosixPath: + raw = _nonempty_string(value, label) + path = PurePosixPath(raw) + if ( + path.is_absolute() + or PureWindowsPath(raw).drive + or "\\" in raw + or "\0" in raw + or ".." in path.parts + or raw != path.as_posix() + or not path.parts + ): + raise AuditFormatError(f"{label} must be a normalized repository-relative POSIX path: {raw!r}") + return path + + +def _reviewed_changes(value: object, label: str) -> Mapping[str, Mapping[str, str]]: + if not isinstance(value, dict): + raise AuditFormatError(f"{label} must be a mapping") + for category, reasons in value.items(): + _nonempty_string(category, label) + if not isinstance(reasons, dict): + raise AuditFormatError(f"{label}.{category} must be a mapping") + for fingerprint, reason in reasons.items(): + _nonempty_string(fingerprint, label) + _nonempty_string(reason, label) + return value + + +def load_inventory(path: Path) -> PrePagesInventory: + try: + data = yaml.safe_load(path.read_text(encoding="utf-8")) + except (OSError, UnicodeError, yaml.YAMLError) as error: + raise AuditFormatError(f"{path}: cannot read inventory: {error}") from error + data = _fields( + data, + {"version", "baseline_commit", "pages_commit", "rename_similarity", "documents", "dispositions"}, + str(path), + ) + if type(data["version"]) is not int or data["version"] != 1: + raise AuditFormatError("version must be 1") + baseline = _commit_hash(data["baseline_commit"], "baseline_commit") + pages = _commit_hash(data["pages_commit"], "pages_commit") + similarity = data["rename_similarity"] + if type(similarity) is not int or not 1 <= similarity <= 100: + raise AuditFormatError("rename_similarity must be an integer between 1 and 100") + if not isinstance(data["documents"], list): + raise AuditFormatError("documents must be a list") + + documents: list[BaselineDocument] = [] + seen: dict[str, set[PurePosixPath]] = {"baseline_path": set(), "pages_path": set()} + for index, raw in enumerate(data["documents"]): + label = f"documents[{index}]" + raw = _fields(raw, {"baseline_path", "pages_path"}, label, {"reviewed_changes"}) + paths = {} + for field in seen: + value = _relative_path(raw[field], f"{label}.{field}") + if value in seen[field]: + raise AuditFormatError(f"{label}: duplicate {field}: {value}") + seen[field].add(value) + paths[field] = value + documents.append(BaselineDocument( + **paths, + reviewed_changes=_reviewed_changes(raw.get("reviewed_changes", {}), f"{label}.reviewed_changes"), + )) + + if not isinstance(data["dispositions"], dict): + raise AuditFormatError("dispositions must be a mapping") + dispositions = {} + for raw_path, raw in data["dispositions"].items(): + baseline_path = _relative_path(raw_path, "dispositions path") + label = f"dispositions.{raw_path}" + raw = _fields(raw, {"status", "current_paths", "reason"}, label) + status = raw["status"] + if not isinstance(status, str) or status not in { + "excluded-local-state", "replaced-summary", "replaced-test", + }: + raise AuditFormatError(f"{label}.status is not a reviewed disposition status") + reason = _nonempty_string(raw["reason"], f"{label}.reason") + current = raw["current_paths"] + if not isinstance(current, list): + raise AuditFormatError(f"{label}.current_paths must be a list") + if (status == "excluded-local-state") != (not current): + raise AuditFormatError( + f"{label}.current_paths must be empty only for excluded-local-state" + ) + paths = tuple(_relative_path(item, f"{label}.current_paths") for item in current) + dispositions[baseline_path] = ReviewedDisposition(status, paths, reason) + return PrePagesInventory(baseline, pages, similarity, tuple(documents), dispositions) + + +def _git(repo_root: Path, *arguments: str) -> bytes: + result = subprocess.run( + ["git", "-C", str(repo_root), *arguments], + check=False, + capture_output=True, + ) + if result.returncode: + detail = result.stderr.decode("utf-8", errors="replace").strip() + raise AuditFormatError( + f"Git {' '.join(arguments)} failed: {detail}. " + "Ensure the exact commit is available with full history (fetch-depth: 0)." + ) + return result.stdout + + +def _require_commit(repo_root: Path, commit: str) -> None: + _commit_hash(commit, "commit") + if _git(repo_root, "cat-file", "-t", commit).strip() != b"commit": + raise AuditFormatError(f"{commit}: expected a Git commit object") + + +def git_bytes(repo_root: Path, commit: str, path: PurePosixPath) -> bytes: + _require_commit(repo_root, commit) + path = _relative_path(path.as_posix(), "Git path") + return _git(repo_root, "cat-file", "blob", f"{commit}:{path.as_posix()}") + + +def git_text(repo_root: Path, commit: str, path: PurePosixPath) -> str: + return git_bytes(repo_root, commit, path).decode("utf-8") + + +def baseline_paths(repo_root: Path, inventory: PrePagesInventory) -> tuple[PurePosixPath, ...]: + _require_commit(repo_root, inventory.baseline_commit) + output = _git(repo_root, "ls-tree", "-r", "--name-only", "-z", inventory.baseline_commit) + return tuple(PurePosixPath(path) for path in output.decode("utf-8").split("\0") if path) + + +def resolve_current_documents( + repo_root: Path, inventory: PrePagesInventory +) -> Mapping[PurePosixPath, Document]: + if len(inventory.documents) != 62: + raise AuditFormatError(f"Expected 62 inventory documents, found {len(inventory.documents)}") + try: + taxonomy = load_taxonomy(repo_root / "docs-taxonomy.yml") + catalog = build_topic_catalog(repo_root / "docs", taxonomy) + except (DocumentFormatError, OSError, yaml.YAMLError) as error: + raise AuditFormatError(f"Cannot resolve current documents: {error}") from error + if len(catalog.documents) != 62: + raise AuditFormatError( + f"Expected 62 current public documents, found {len(catalog.documents)}" + ) + by_path = {document.relative_path: document for document in catalog.documents} + resolved: dict[PurePosixPath, Document] = {} + used: set[PurePosixPath] = set() + for document in inventory.documents: + git_bytes(repo_root, inventory.baseline_commit, document.baseline_path) + git_bytes(repo_root, inventory.pages_commit, PurePosixPath("docs") / document.pages_path) + canonical = catalog.redirects.get(document.pages_path) + if canonical is None: + raise AuditFormatError(f"{document.pages_path}: missing initial Pages redirect_from") + if canonical in used: + raise AuditFormatError(f"{canonical}: current public document used more than once") + if document.baseline_path in resolved: + raise AuditFormatError(f"duplicate baseline_path: {document.baseline_path}") + resolved[document.baseline_path] = by_path[canonical] + used.add(canonical) + if used != set(by_path): + raise AuditFormatError("Every current public document must be used once") + return MappingProxyType(resolved) + + +def classify_baseline_files( + repo_root: Path, inventory: PrePagesInventory +) -> tuple[GitFileDisposition, ...]: + paths = baseline_paths(repo_root, inventory) + classified = { + path: GitFileDisposition(path, "unchanged", (path,)) + for path in paths + } + output = _git( + repo_root, "diff", f"--find-renames={inventory.rename_similarity}%", + "--name-status", "-z", inventory.baseline_commit, "HEAD", "--", + ) + fields = iter(output.decode("utf-8").split("\0")[:-1]) + deleted = set() + for status in fields: + try: + source = PurePosixPath(next(fields)) + target = PurePosixPath(next(fields)) if status.startswith("R") else source + except StopIteration as error: + raise AuditFormatError("Truncated Git name-status output") from error + if status == "A": + continue + if source not in classified: + raise AuditFormatError(f"Git diff path is not in baseline: {source}") + if status == "D": + deleted.add(source) + elif status in {"M", "T"}: + classified[source] = GitFileDisposition(source, "modified", (source,)) + elif status.startswith("R"): + classified[source] = GitFileDisposition(source, "renamed", (target,)) + else: + raise AuditFormatError(f"{source}: unsupported Git status {status}") + + for path, disposition in inventory.dispositions.items(): + if path not in classified: + raise AuditFormatError(f"Reviewed disposition is not in baseline: {path}") + for current in disposition.current_paths: + if not (repo_root / current).is_file(): + raise AuditFormatError(f"{path}: reviewed current path does not exist: {current}") + classified[path] = GitFileDisposition(path, "reviewed", disposition.current_paths) + unreviewed = deleted - inventory.dispositions.keys() + if unreviewed: + raise AuditFormatError( + "unreviewed deleted baseline paths: " + ", ".join(str(path) for path in sorted(unreviewed)) + ) + return tuple(classified[path] for path in paths) diff --git a/scripts/docs/pre_pages_inventory.yml b/scripts/docs/pre_pages_inventory.yml new file mode 100644 index 00000000..c8a1b4bd --- /dev/null +++ b/scripts/docs/pre_pages_inventory.yml @@ -0,0 +1,223 @@ +version: 1 +baseline_commit: a4e680116db1016a698e64baae9efde858a8eafa +pages_commit: 9ace966742ff92a00fc84bcc21529af47b8661af +rename_similarity: 20 +documents: + - baseline_path: aifoundry/agent_framework_2026_customer_brief.md + pages_path: research/microsoft-foundry/agent-framework-2026/index.md + reviewed_changes: {} + - baseline_path: aifoundry/codex_closednetwork.md + pages_path: guides/microsoft-foundry/codex-closed-network/index.md + reviewed_changes: {} + - baseline_path: aifoundry/foundry_local.md + pages_path: guides/microsoft-foundry/foundry-local-air-gapped/index.md + reviewed_changes: {} + - baseline_path: aisearch-v2/README.md + pages_path: guides/azure-ai-search/korean-analyzer-comparison/index.md + reviewed_changes: {} + - baseline_path: aisearch/custom_vectorization/01_custom_embedding_guide.md + pages_path: guides/azure-ai-search/custom-embedding-ingestion/index.md + reviewed_changes: {} + - baseline_path: aisearch/custom_vectorization/02_custom_vectorization.md + pages_path: guides/azure-ai-search/custom-web-api-vectorization/index.md + reviewed_changes: {} + - baseline_path: aisearch/custom_vectorization/03_gpu_vllm_rag_guide.md + pages_path: guides/azure-ai-search/gpu-vllm-rag/index.md + reviewed_changes: {} + - baseline_path: aisearch/custom_vectorization/ref_a10_vs_t4_embedding_benchmark.md + pages_path: research/azure-ai-search/a10-vs-t4-embedding-benchmark/index.md + reviewed_changes: {} + - baseline_path: aisearch/custom_vectorization/ref_bge_m3_vs_qwen3_comparison.md + pages_path: research/azure-ai-search/bge-m3-vs-qwen3-embedding/index.md + reviewed_changes: {} + - baseline_path: aisearch/custom_vectorization/ref_chunking_strategies_research.md + pages_path: research/azure-ai-search/rag-chunking-strategies/index.md + reviewed_changes: {} + - baseline_path: aisearch/eventual_consistency_reindex.md + pages_path: cases/azure-ai-search/eventual-consistency-reindex/index.md + reviewed_changes: {} + - baseline_path: aks/affinity.md + pages_path: guides/azure-kubernetes-service/pod-affinity-distribution/index.md + reviewed_changes: {} + - baseline_path: aks/agent-pool.md + pages_path: guides/azure-kubernetes-service/pod-scheduling-agent-pools/index.md + reviewed_changes: {} + - baseline_path: aks/argocd_image_updater.md + pages_path: guides/azure-kubernetes-service/argocd-image-updater-acr/index.md + reviewed_changes: {} + - baseline_path: aks/authorization-troubleshooting.md + pages_path: guides/azure-kubernetes-service/authorization-troubleshooting/index.md + reviewed_changes: {} + - baseline_path: aks/cni-overlay-nsg.md + pages_path: guides/azure-kubernetes-service/cni-overlay-nsg/index.md + reviewed_changes: {} + - baseline_path: aks/file_io_throttling.md + pages_path: cases/azure-kubernetes-service/file-io-throttling/index.md + reviewed_changes: {} + - baseline_path: aks/file_io_throttling2.md + pages_path: cases/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md + reviewed_changes: {} + - baseline_path: aks/kaito_opensource_model.md + pages_path: guides/azure-kubernetes-service/kaito-open-source-model/index.md + reviewed_changes: {} + - baseline_path: aks/local_dev_loop.md + pages_path: guides/azure-kubernetes-service/remote-cluster-local-development/index.md + reviewed_changes: {} + - baseline_path: aks/memray_leak_profiling.md + pages_path: guides/azure-kubernetes-service/python-memory-leak-memray/index.md + reviewed_changes: {} + - baseline_path: aks/pod_db_query_latency.md + pages_path: cases/azure-kubernetes-service/pod-database-query-latency/index.md + reviewed_changes: {} + - baseline_path: aks/pyroscope_anf_s3_deployment.md + pages_path: guides/azure-kubernetes-service/pyroscope-anf-s3/index.md + reviewed_changes: {} + - baseline_path: aks/spot_gpu_kaito_llm.md + pages_path: guides/azure-kubernetes-service/spot-h100-kaito/index.md + reviewed_changes: {} + - baseline_path: aks/workload_identity_databricks_keyless.md + pages_path: guides/azure-kubernetes-service/workload-identity-databricks/index.md + reviewed_changes: {} + - baseline_path: appgw/sse_response_buffer_disable.md + pages_path: guides/azure-application-gateway/sse-response-buffering/index.md + reviewed_changes: {} + - baseline_path: appgw/waf_custom_rule_ip_whitelist.md + pages_path: guides/azure-application-gateway/waf-path-ip-allowlist/index.md + reviewed_changes: {} + - baseline_path: architect/request_in_second.md + pages_path: guides/azure-architecture/response-time-optimization/index.md + reviewed_changes: {} + - baseline_path: automation/portal-cli-limitations.md + pages_path: guides/azure-automation/portal-cli-limitations/index.md + reviewed_changes: {} + - baseline_path: azureblob/mobile_resumable_upload_tus.md + pages_path: guides/azure-storage/mobile-resumable-upload-tus/index.md + reviewed_changes: {} + - baseline_path: cosmosdb/nodejs/client.md + pages_path: guides/azure-cosmos-db/nodejs-client-optimization/index.md + reviewed_changes: {} + - baseline_path: cosmosdb/nodejs/point_read.md + pages_path: guides/azure-cosmos-db/point-read-optimization/index.md + reviewed_changes: {} + - baseline_path: cosmosdb/nodejs/query_thread_with_low_core.md + pages_path: guides/azure-cosmos-db/nodejs-dns-lookup-bottleneck/index.md + reviewed_changes: {} + - baseline_path: develop/nodejs/fileio_issue_cpu_overhead.md + pages_path: guides/application-development/nodejs-file-io-cpu/index.md + reviewed_changes: {} + - baseline_path: hdinsight/kafka-catchup-sku-fetch-benchmark.md + pages_path: research/azure-hdinsight/kafka-catchup-sku-fetch-benchmark/index.md + reviewed_changes: {} + - baseline_path: loadtest/loadtest_locust_appgw_aks.md + pages_path: guides/azure-load-testing/locust-appgw-aks-private/index.md + reviewed_changes: {} + - baseline_path: memory/agent-memory/01-memory-taxonomy.md + pages_path: research/microsoft-foundry/agent-memory-taxonomy/index.md + reviewed_changes: {} + - baseline_path: memory/agent-memory/02-architecture-patterns.md + pages_path: research/microsoft-foundry/agent-memory-architecture-patterns/index.md + reviewed_changes: {} + - baseline_path: memory/agent-memory/03-pipeline-and-retrieval.md + pages_path: research/microsoft-foundry/agent-memory-pipeline-retrieval/index.md + reviewed_changes: {} + - baseline_path: memory/agent-memory/04-frameworks.md + pages_path: research/microsoft-foundry/agent-memory-frameworks/index.md + reviewed_changes: {} + - baseline_path: memory/agent-memory/05-production-evaluation.md + pages_path: research/microsoft-foundry/agent-memory-production-evaluation/index.md + reviewed_changes: {} + - baseline_path: memory/agent-memory/06-commerce-application.md + pages_path: research/microsoft-foundry/agent-memory-commerce/index.md + reviewed_changes: {} + - baseline_path: memory/agent-memory/README.md + pages_path: research/microsoft-foundry/agent-memory-overview/index.md + reviewed_changes: {} + - baseline_path: memory/foundry-gpt-memory-v1.md + pages_path: guides/microsoft-foundry/gpt-memory-layer/index.md + reviewed_changes: {} + - baseline_path: monitor/azure-monitor-dynamic-thresholds-brief.md + pages_path: research/azure-monitor/dynamic-thresholds-brief/index.md + reviewed_changes: {} + - baseline_path: monitor/azure-sre-agent.md + pages_path: guides/azure-monitor/azure-sre-agent-overview/index.md + reviewed_changes: {} + - baseline_path: monitor/hdinsight-kafka-monitoring.md + pages_path: research/azure-monitor/hdinsight-kafka-monitoring-options/index.md + reviewed_changes: {} + - baseline_path: monitor/hdinsight-kafka-prometheus-grafana.md + pages_path: guides/azure-monitor/hdinsight-kafka-prometheus-grafana/index.md + reviewed_changes: {} + - baseline_path: monitor/otel-monitor-setup.md + pages_path: guides/azure-monitor/aks-private-opentelemetry/index.md + reviewed_changes: {} + - baseline_path: monitor/sre-agent-event-lab/README.md + pages_path: labs/azure-monitor/sre-agent-event-lab/index.md + reviewed_changes: {} + - baseline_path: monitor/sre-agent-event-lab/dynamic-thresholds.md + pages_path: guides/azure-monitor/sre-agent-dynamic-thresholds/index.md + reviewed_changes: {} + - baseline_path: monitor/sre-agent-event-lab/guides/01-agent-setup.md + pages_path: labs/azure-monitor/sre-agent-event-lab-setup/index.md + reviewed_changes: {} + - baseline_path: monitor/sre-agent-event-lab/guides/02-scenario-s1.md + pages_path: labs/azure-monitor/sre-agent-scenario-http-500/index.md + reviewed_changes: {} + - baseline_path: monitor/sre-agent-event-lab/guides/03-scenario-s2.md + pages_path: labs/azure-monitor/sre-agent-scenario-latency/index.md + reviewed_changes: {} + - baseline_path: monitor/sre-agent-event-lab/guides/04-scenario-s3.md + pages_path: labs/azure-monitor/sre-agent-scenario-blob-permission/index.md + reviewed_changes: {} + - baseline_path: monitor/sre-agent-event-lab/guides/05-results.md + pages_path: labs/azure-monitor/sre-agent-results/index.md + reviewed_changes: {} + - baseline_path: monitor/sre-agent-event-lab/runbooks/incident-response.md + pages_path: guides/azure-monitor/sre-agent-incident-runbook/index.md + reviewed_changes: {} + - baseline_path: monitor/sre-agent-event-lab/validation-results.md + pages_path: research/azure-monitor/sre-agent-validation-results/index.md + reviewed_changes: {} + - baseline_path: mysql/nodejs/db_upgrade_zero_downtime.md + pages_path: guides/azure-database-for-mysql/blue-green-upgrade/index.md + reviewed_changes: {} + - baseline_path: mysql/nodejs/read_write_routing.md + pages_path: guides/azure-database-for-mysql/nodejs-read-write-routing/index.md + reviewed_changes: {} + - baseline_path: ptu_lb/ptu-lb-test.md + pages_path: guides/azure-openai/adaptive-ptu-load-balancing/index.md + reviewed_changes: {} + - baseline_path: redis/nodejs/cluster_failover_recovery.md + pages_path: cases/azure-managed-redis/cluster-failover-recovery/index.md + reviewed_changes: {} +dispositions: + .azure/deployment-plan.md: + status: excluded-local-state + current_paths: [] + reason: Local Azure deployment planning state is intentionally not public. + .azure/validate-status.json: + status: excluded-local-state + current_paths: [] + reason: Local Azure validation state is intentionally not public. + memory/README.md: + status: replaced-summary + current_paths: + - docs/services/microsoft-foundry/agent-memory/index.md + - docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + - docs/services/microsoft-foundry/gpt-memory-layer/samples/architecture/README.md + reason: The service-level index was split into the canonical topic overview and owning sample documentation. + ptu_lb/README.md: + status: replaced-summary + current_paths: + - docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/README.md + reason: The link-only README was replaced by the owning topic sample README. + monitor/sre-agent-event-lab/scripts/tests/test_lab_guides.py: + status: replaced-test + current_paths: + - docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_briefing_docs.py + - tests/docs/test_reader_navigation.py + reason: Guide-layout assertions were replaced by canonical topic, navigation, and briefing-document contracts. + monitor/sre-agent-event-lab/scripts/tests/test_repo_readme.py: + status: replaced-test + current_paths: + - docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_repo_readme.py + reason: The repository discovery contract test moved with the sample and now targets canonical topic discovery. diff --git a/tests/docs/test_pre_pages.py b/tests/docs/test_pre_pages.py new file mode 100644 index 00000000..835cdb1b --- /dev/null +++ b/tests/docs/test_pre_pages.py @@ -0,0 +1,628 @@ +from __future__ import annotations + +from collections import Counter +from dataclasses import FrozenInstanceError, replace +from pathlib import Path, PurePosixPath +import re +import subprocess + +import pytest +import yaml + +from scripts.docs import pre_pages +from scripts.docs.pre_pages import ( + AuditFormatError, + BaselineDocument, + GitFileDisposition, + PrePagesInventory, + ReviewedDisposition, + git_bytes, + git_text, + load_inventory, +) + + +ROOT = Path(__file__).parents[2] +BASELINE = "a4e680116db1016a698e64baae9efde858a8eafa" +PAGES = "9ace966742ff92a00fc84bcc21529af47b8661af" +MANIFEST = ROOT / "scripts/docs/pre_pages_inventory.yml" + + +def git(repo: Path, *arguments: str) -> bytes: + return subprocess.run( + ["git", "-C", str(repo), *arguments], + check=True, + capture_output=True, + ).stdout + + +def commit(repo: Path, message: str) -> str: + git(repo, "add", "--all") + git(repo, "-c", "commit.gpgsign=false", "commit", "-qm", message) + return git(repo, "rev-parse", "HEAD").decode().strip() + + +@pytest.fixture +def history(tmp_path: Path) -> tuple[Path, str, str]: + repo = tmp_path / "repo" + repo.mkdir() + git(repo, "init", "-q") + git(repo, "config", "user.name", "Inventory Tests") + git(repo, "config", "user.email", "inventory@example.com") + (repo / "한글 문서.md").write_bytes("첫 문장\r\n둘째 문장\n".encode()) + (repo / "binary.bin").write_bytes(b"\x00\xff\r\n\x80") + (repo / "-literal ; $(touch injected)\tfile.md").write_text("literal\n") + baseline = commit(repo, "Baseline") + (repo / "added.md").write_text("New file\n") + pages = commit(repo, "Pages") + return repo, baseline, pages + + +@pytest.fixture +def manifest_data() -> dict: + return { + "version": 1, + "baseline_commit": BASELINE, + "pages_commit": PAGES, + "rename_similarity": 20, + "documents": [ + { + "baseline_path": "old/guide.md", + "pages_path": "guides/service/topic/index.md", + "reviewed_changes": {"prose": {"fingerprint": "Preserved in sample README."}}, + } + ], + "dispositions": { + ".azure/state.json": { + "status": "excluded-local-state", + "current_paths": [], + "reason": "Local state is intentionally not public.", + }, + "old/README.md": { + "status": "replaced-summary", + "current_paths": ["docs/services/service/topic/index.md"], + "reason": "Replaced by the canonical topic overview.", + }, + "old/test.py": { + "status": "replaced-test", + "current_paths": ["tests/docs/test_topics.py"], + "reason": "Replaced by topic contract assertions.", + }, + }, + } + + +def load_data(tmp_path: Path, data: object) -> PrePagesInventory: + path = tmp_path / "inventory.yml" + path.write_text(yaml.safe_dump(data, allow_unicode=True), encoding="utf-8") + return load_inventory(path) + + +def test_inventory_loads_deeply_immutable_models(tmp_path: Path, manifest_data: dict) -> None: + inventory = load_data(tmp_path, manifest_data) + assert isinstance(inventory, PrePagesInventory) + assert inventory.baseline_commit == BASELINE + assert inventory.pages_commit == PAGES + assert inventory.rename_similarity == 20 + document = inventory.documents[0] + assert isinstance(document, BaselineDocument) + assert document.baseline_path == PurePosixPath("old/guide.md") + assert document.pages_path == PurePosixPath("guides/service/topic/index.md") + disposition = inventory.dispositions[PurePosixPath("old/README.md")] + assert isinstance(disposition, ReviewedDisposition) + assert disposition.current_paths == (PurePosixPath("docs/services/service/topic/index.md"),) + file = GitFileDisposition(document.baseline_path, "unchanged", (document.baseline_path,)) + for model, field, value in ( + (inventory, "rename_similarity", 100), + (document, "baseline_path", PurePosixPath("other.md")), + (disposition, "reason", "changed"), + (file, "status", "modified"), + ): + with pytest.raises(FrozenInstanceError): + setattr(model, field, value) + with pytest.raises(TypeError): + inventory.dispositions[PurePosixPath("other.md")] = disposition + with pytest.raises(TypeError): + document.reviewed_changes["prose"]["fingerprint"] = "changed" + with pytest.raises(TypeError): + document.reviewed_changes["headings"] = {} + + +@pytest.mark.parametrize("field", ["baseline_commit", "pages_commit"]) +@pytest.mark.parametrize("value", [None, "", "a4e6801", "A" * 40, "g" * 40, "a" * 39, 123]) +def test_inventory_requires_full_lowercase_commit_hashes( + tmp_path: Path, manifest_data: dict, field: str, value: object +) -> None: + manifest_data[field] = value + with pytest.raises(AuditFormatError, match=field): + load_data(tmp_path, manifest_data) + + +@pytest.mark.parametrize( + "field", + ["version", "baseline_commit", "pages_commit", "rename_similarity", "documents", "dispositions"], +) +def test_inventory_requires_all_top_level_fields( + tmp_path: Path, manifest_data: dict, field: str +) -> None: + del manifest_data[field] + with pytest.raises(AuditFormatError, match=field): + load_data(tmp_path, manifest_data) + + +@pytest.mark.parametrize( + ("field", "value"), + [ + ("extra", True), ("version", 2), ("version", True), + ("rename_similarity", 0), ("rename_similarity", 101), + ("rename_similarity", "20"), ("rename_similarity", True), + ("documents", {}), ("dispositions", []), + ], +) +def test_inventory_rejects_invalid_schema( + tmp_path: Path, manifest_data: dict, field: str, value: object +) -> None: + manifest_data[field] = value + with pytest.raises(AuditFormatError, match=field): + load_data(tmp_path, manifest_data) + + +@pytest.mark.parametrize("field", ["baseline_path", "pages_path"]) +def test_inventory_rejects_duplicate_document_paths( + tmp_path: Path, manifest_data: dict, field: str +) -> None: + duplicate = { + "baseline_path": "another.md", + "pages_path": "guides/service/another/index.md", + } + duplicate[field] = manifest_data["documents"][0][field] + manifest_data["documents"].append(duplicate) + with pytest.raises(AuditFormatError, match=f"duplicate {field}"): + load_data(tmp_path, manifest_data) + + +@pytest.mark.parametrize("value", ["", "/root.md", "../secret.md", "a/../b.md", "a\\b.md", "C:/a.md", "a\0b.md"]) +@pytest.mark.parametrize("field", ["baseline_path", "pages_path"]) +def test_inventory_rejects_unsafe_paths( + tmp_path: Path, manifest_data: dict, field: str, value: str +) -> None: + manifest_data["documents"][0][field] = value + with pytest.raises(AuditFormatError, match=field): + load_data(tmp_path, manifest_data) + + +@pytest.mark.parametrize( + ("field", "value"), + [ + ("status", "deleted"), ("status", None), ("reason", ""), + ("reason", None), ("current_paths", []), ("current_paths", "target.md"), + ("current_paths", ["/outside.md"]), + ], +) +def test_inventory_rejects_invalid_reviewed_dispositions( + tmp_path: Path, manifest_data: dict, field: str, value: object +) -> None: + manifest_data["dispositions"]["old/README.md"][field] = value + with pytest.raises(AuditFormatError, match=field): + load_data(tmp_path, manifest_data) + + +@pytest.mark.parametrize("field", ["status", "current_paths", "reason"]) +def test_inventory_requires_disposition_fields( + tmp_path: Path, manifest_data: dict, field: str +) -> None: + del manifest_data["dispositions"]["old/README.md"][field] + with pytest.raises(AuditFormatError, match=field): + load_data(tmp_path, manifest_data) + + +def test_excluded_local_state_cannot_have_replacements(tmp_path: Path, manifest_data: dict) -> None: + manifest_data["dispositions"][".azure/state.json"]["current_paths"] = ["public.md"] + with pytest.raises(AuditFormatError, match="current_paths"): + load_data(tmp_path, manifest_data) + + +@pytest.mark.parametrize("value", [[], {"prose": []}, {"prose": {"fingerprint": ""}}]) +def test_reviewed_changes_require_nested_reason_mappings( + tmp_path: Path, manifest_data: dict, value: object +) -> None: + manifest_data["documents"][0]["reviewed_changes"] = value + with pytest.raises(AuditFormatError, match="reviewed_changes"): + load_data(tmp_path, manifest_data) + + +def test_reviewed_changes_default_to_empty(tmp_path: Path, manifest_data: dict) -> None: + del manifest_data["documents"][0]["reviewed_changes"] + assert load_data(tmp_path, manifest_data).documents[0].reviewed_changes == {} + + +@pytest.mark.parametrize("text", ["[]", "version: [", "!!python/object/apply:os.system ['false']"]) +def test_inventory_reports_invalid_or_unsafe_yaml(tmp_path: Path, text: str) -> None: + path = tmp_path / "invalid.yml" + path.write_text(text) + with pytest.raises(AuditFormatError): + load_inventory(path) + + +def test_git_readers_preserve_text_and_binary(history: tuple[Path, str, str]) -> None: + repo, baseline, _ = history + assert git_text(repo, baseline, PurePosixPath("한글 문서.md")) == "첫 문장\r\n둘째 문장\n" + assert git_bytes(repo, baseline, PurePosixPath("binary.bin")) == b"\x00\xff\r\n\x80" + + +def test_git_reader_treats_shell_metacharacters_literally( + history: tuple[Path, str, str], monkeypatch: pytest.MonkeyPatch +) -> None: + repo, baseline, _ = history + run = subprocess.run + calls = [] + + def checked_run(arguments, **kwargs): + assert isinstance(arguments, list) + assert kwargs.get("shell", False) is False + calls.append(arguments) + return run(arguments, **kwargs) + + monkeypatch.setattr(subprocess, "run", checked_run) + path = PurePosixPath("-literal ; $(touch injected)\tfile.md") + assert git_text(repo, baseline, path) == "literal\n" + assert any(f"{baseline}:{path.as_posix()}" in arguments for arguments in calls) + assert not (repo / "injected").exists() + + +@pytest.mark.parametrize("reader", [git_text, git_bytes]) +def test_missing_commit_error_includes_exact_hash( + history: tuple[Path, str, str], reader +) -> None: + repo, _, _ = history + missing = "0" * 40 + with pytest.raises(AuditFormatError, match=missing): + reader(repo, missing, PurePosixPath("binary.bin")) + + +def test_shallow_history_error_names_missing_commit( + history: tuple[Path, str, str], tmp_path: Path +) -> None: + repo, baseline, _ = history + shallow = tmp_path / "shallow" + git(tmp_path, "clone", "-q", "--depth=1", repo.as_uri(), str(shallow)) + with pytest.raises(AuditFormatError, match=baseline) as error: + git_bytes(shallow, baseline, PurePosixPath("binary.bin")) + assert "history" in str(error.value).lower() + + +def test_missing_blob_error_names_commit_and_path(history: tuple[Path, str, str]) -> None: + repo, baseline, _ = history + with pytest.raises(AuditFormatError, match=baseline) as error: + git_bytes(repo, baseline, PurePosixPath("missing.md")) + assert "missing.md" in str(error.value) + + +@pytest.mark.parametrize("object_kind", ["tag", "tree", "blob"]) +def test_git_readers_require_commit_objects_not_other_full_hashes( + history: tuple[Path, str, str], object_kind: str +) -> None: + repo, baseline, _ = history + if object_kind == "tag": + git(repo, "-c", "tag.gpgsign=false", "tag", "-a", "snapshot", "-m", "Snapshot", baseline) + object_hash = git(repo, "rev-parse", "snapshot").decode().strip() + else: + revision = f"{baseline}^{{tree}}" if object_kind == "tree" else f"{baseline}:binary.bin" + object_hash = git(repo, "rev-parse", revision).decode().strip() + with pytest.raises(AuditFormatError, match=object_hash): + git_bytes(repo, object_hash, PurePosixPath("binary.bin")) + + +def test_fixed_manifest_matches_all_62_initial_pages_renames() -> None: + assert MANIFEST.is_file(), "The fixed pre-Pages manifest must exist" + inventory = load_inventory(MANIFEST) + assert inventory.baseline_commit == BASELINE + assert inventory.pages_commit == PAGES + assert inventory.rename_similarity == 20 + output = git( + ROOT, "diff", "--find-renames=30%", "--name-status", "-z", BASELINE, PAGES, "--", "*.md" + ).decode().split("\0") + expected = set() + index = 0 + while index < len(output) - 1: + status, source = output[index:index + 2] + index += 2 + if status.startswith("R"): + target = output[index] + index += 1 + if re.fullmatch(r"docs/(cases|guides|labs|research)/[^/]+/[^/]+/index\.md", target): + expected.add((PurePosixPath(source), PurePosixPath(target.removeprefix("docs/")))) + actual = {(doc.baseline_path, doc.pages_path) for doc in inventory.documents} + assert len(expected) == len(inventory.documents) == len(actual) == 62 + assert actual == expected + + +def test_fixed_manifest_has_only_the_six_exact_reviewed_dispositions() -> None: + assert MANIFEST.is_file(), "The fixed pre-Pages manifest must exist" + expected = { + ".azure/deployment-plan.md": ( + "excluded-local-state", (), + "Local Azure deployment planning state is intentionally not public.", + ), + ".azure/validate-status.json": ( + "excluded-local-state", (), + "Local Azure validation state is intentionally not public.", + ), + "memory/README.md": ( + "replaced-summary", + ( + "docs/services/microsoft-foundry/agent-memory/index.md", + "docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md", + "docs/services/microsoft-foundry/gpt-memory-layer/samples/architecture/README.md", + ), + "The service-level index was split into the canonical topic overview and owning sample documentation.", + ), + "ptu_lb/README.md": ( + "replaced-summary", + ("docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/README.md",), + "The link-only README was replaced by the owning topic sample README.", + ), + "monitor/sre-agent-event-lab/scripts/tests/test_lab_guides.py": ( + "replaced-test", + ( + "docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_briefing_docs.py", + "tests/docs/test_reader_navigation.py", + ), + "Guide-layout assertions were replaced by canonical topic, navigation, and briefing-document contracts.", + ), + "monitor/sre-agent-event-lab/scripts/tests/test_repo_readme.py": ( + "replaced-test", + ("docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_repo_readme.py",), + "The repository discovery contract test moved with the sample and now targets canonical topic discovery.", + ), + } + actual = { + path.as_posix(): ( + value.status, tuple(item.as_posix() for item in value.current_paths), value.reason, + ) + for path, value in load_inventory(MANIFEST).dispositions.items() + } + assert actual == expected + + +def lineage_api(name: str): + assert hasattr(pre_pages, name), f"The {name} lineage API must exist" + return getattr(pre_pages, name) + + +def small_inventory(baseline: str, pages: str, dispositions: dict | None = None) -> PrePagesInventory: + return PrePagesInventory(baseline, pages, 20, (), dispositions or {}) + + +def test_baseline_paths_include_every_file_and_preserve_literal_names( + history: tuple[Path, str, str] +) -> None: + repo, baseline, pages = history + paths = lineage_api("baseline_paths")(repo, small_inventory(baseline, pages)) + assert isinstance(paths, tuple) + assert set(paths) == { + PurePosixPath("한글 문서.md"), PurePosixPath("binary.bin"), + PurePosixPath("-literal ; $(touch injected)\tfile.md"), + } + + +@pytest.fixture +def file_history(history: tuple[Path, str, str]) -> tuple[Path, PrePagesInventory]: + repo, _, baseline = history + (repo / "한글 문서.md").write_text("수정된 문장\n") + (repo / "-literal ; $(touch injected)\tfile.md").rename(repo / "renamed\t\n한글.md") + (repo / "added.md").unlink() + (repo / "replacement.md").write_text("Canonical replacement overview.\n") + (repo / "new-only.md").write_text("Not a baseline file.\n") + pages = commit(repo, "Current") + return repo, small_inventory( + baseline, pages, + { + PurePosixPath("added.md"): ReviewedDisposition( + "replaced-summary", (PurePosixPath("replacement.md"),), "Canonical replacement overview.", + ) + }, + ) + + +def test_classification_covers_unchanged_modified_renamed_and_reviewed_files( + file_history: tuple[Path, PrePagesInventory] +) -> None: + repo, inventory = file_history + actual = lineage_api("classify_baseline_files")(repo, inventory) + assert isinstance(actual, tuple) + assert {item.baseline_path: (item.status, item.current_paths) for item in actual} == { + PurePosixPath("binary.bin"): ("unchanged", (PurePosixPath("binary.bin"),)), + PurePosixPath("한글 문서.md"): ("modified", (PurePosixPath("한글 문서.md"),)), + PurePosixPath("-literal ; $(touch injected)\tfile.md"): ( + "renamed", (PurePosixPath("renamed\t\n한글.md"),), + ), + PurePosixPath("added.md"): ("reviewed", (PurePosixPath("replacement.md"),)), + } + assert len(actual) == 4 + + +def test_classification_rejects_unreviewed_deletion(file_history: tuple[Path, PrePagesInventory]) -> None: + repo, inventory = file_history + with pytest.raises(AuditFormatError, match="unreviewed.*added.md"): + lineage_api("classify_baseline_files")(repo, replace(inventory, dispositions={})) + + +def test_classification_rejects_disposition_outside_baseline( + file_history: tuple[Path, PrePagesInventory] +) -> None: + repo, inventory = file_history + dispositions = dict(inventory.dispositions) + dispositions[PurePosixPath("not-in-baseline.md")] = ReviewedDisposition( + "excluded-local-state", (), "Local state.", + ) + with pytest.raises(AuditFormatError, match="not-in-baseline.md"): + lineage_api("classify_baseline_files")(repo, replace(inventory, dispositions=dispositions)) + + +def test_classification_rejects_missing_replacement_path(file_history: tuple[Path, PrePagesInventory]) -> None: + repo, inventory = file_history + (repo / "replacement.md").unlink() + with pytest.raises(AuditFormatError, match="replacement.md"): + lineage_api("classify_baseline_files")(repo, inventory) + + +def test_classification_accepts_reviewed_local_state_exclusion( + file_history: tuple[Path, PrePagesInventory] +) -> None: + repo, inventory = file_history + inventory = replace( + inventory, + dispositions={ + PurePosixPath("added.md"): ReviewedDisposition( + "excluded-local-state", (), "Local state is intentionally not public.", + ) + }, + ) + result = lineage_api("classify_baseline_files")(repo, inventory) + assert next(item for item in result if item.baseline_path == PurePosixPath("added.md")) == ( + GitFileDisposition(PurePosixPath("added.md"), "reviewed", ()) + ) + + +@pytest.fixture +def document_history(history: tuple[Path, str, str]) -> tuple[Path, PrePagesInventory]: + repo, _, _ = history + documents = tuple( + BaselineDocument( + PurePosixPath(f"old/topic-{index:02}.md"), + PurePosixPath(f"guides/service/topic-{index:02}/index.md"), + {}, + ) + for index in range(62) + ) + for document in documents: + path = repo / document.baseline_path + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(f"# {path.stem}\n") + baseline = commit(repo, "Historical documents") + for document in documents: + target = repo / "docs" / document.pages_path + target.parent.mkdir(parents=True, exist_ok=True) + (repo / document.baseline_path).rename(target) + pages = commit(repo, "Initial Pages documents") + for document in documents: + source = repo / "docs" / document.pages_path + target = repo / "docs/services/service" / document.pages_path.parent.name / "index.md" + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text( + "---\ntitle: Canonical topic\nredirect_from:\n" + f" - {document.pages_path}\n---\n{source.read_text()}" + ) + source.unlink() + (repo / "docs-taxonomy.yml").write_text("{}\n") + commit(repo, "Canonical topics") + return repo, PrePagesInventory(baseline, pages, 20, documents, {}) + + +def test_current_documents_resolve_one_to_one_from_initial_pages_redirects( + document_history: tuple[Path, PrePagesInventory] +) -> None: + repo, inventory = document_history + resolved = lineage_api("resolve_current_documents")(repo, inventory) + assert len(resolved) == 62 + assert set(resolved) == {item.baseline_path for item in inventory.documents} + assert len({item.relative_path for item in resolved.values()}) == 62 + for original in inventory.documents: + assert resolved[original.baseline_path].relative_path == ( + PurePosixPath("services/service") / original.pages_path.parent.name / "index.md" + ) + + +@pytest.mark.parametrize("count", [61, 63]) +def test_resolution_requires_exactly_62_inventory_documents( + document_history: tuple[Path, PrePagesInventory], count: int +) -> None: + repo, inventory = document_history + documents = ( + inventory.documents[:61] if count == 61 + else inventory.documents + (inventory.documents[0],) + ) + with pytest.raises(AuditFormatError, match="62 inventory documents"): + lineage_api("resolve_current_documents")(repo, replace(inventory, documents=documents)) + + +@pytest.mark.parametrize("count", [61, 63]) +def test_resolution_requires_exactly_62_current_public_documents( + document_history: tuple[Path, PrePagesInventory], count: int +) -> None: + repo, inventory = document_history + path = repo / "docs/services/service/topic-00/index.md" + if count == 61: + path.unlink() + else: + extra = repo / "docs/services/service/extra/index.md" + extra.parent.mkdir() + extra.write_text("---\ntitle: Extra\n---\n# Extra\n") + with pytest.raises(AuditFormatError, match="62 current public documents"): + lineage_api("resolve_current_documents")(repo, inventory) + + +def test_resolution_rejects_missing_initial_pages_redirect( + document_history: tuple[Path, PrePagesInventory] +) -> None: + repo, inventory = document_history + path = repo / "docs/services/service/topic-00/index.md" + path.write_text("---\ntitle: Missing redirect\n---\n") + with pytest.raises(AuditFormatError, match="guides/service/topic-00/index.md"): + lineage_api("resolve_current_documents")(repo, inventory) + + +def test_resolution_rejects_duplicate_redirect_ownership( + document_history: tuple[Path, PrePagesInventory] +) -> None: + repo, inventory = document_history + path = repo / "docs/services/service/topic-01/index.md" + path.write_text(path.read_text().replace("guides/service/topic-01/", "guides/service/topic-00/")) + with pytest.raises(AuditFormatError, match="redirect_from.*already used"): + lineage_api("resolve_current_documents")(repo, inventory) + + +def test_resolution_rejects_two_baseline_documents_using_one_current_document( + document_history: tuple[Path, PrePagesInventory] +) -> None: + repo, inventory = document_history + first = repo / "docs/services/service/topic-00/index.md" + first.write_text(first.read_text().replace( + "redirect_from:\n", "redirect_from:\n - guides/service/topic-01/index.md\n", + )) + second = repo / "docs/services/service/topic-01/index.md" + second.write_text("---\ntitle: Unused\n---\n") + with pytest.raises(AuditFormatError, match="used more than once"): + lineage_api("resolve_current_documents")(repo, inventory) + + +def test_resolution_requires_baseline_blobs(document_history: tuple[Path, PrePagesInventory]) -> None: + repo, inventory = document_history + documents = ( + replace(inventory.documents[0], baseline_path=PurePosixPath("missing.md")), + *inventory.documents[1:], + ) + with pytest.raises(AuditFormatError, match=inventory.baseline_commit) as error: + lineage_api("resolve_current_documents")(repo, replace(inventory, documents=documents)) + assert "missing.md" in str(error.value) + + +def test_resolution_requires_initial_pages_blobs(document_history: tuple[Path, PrePagesInventory]) -> None: + repo, inventory = document_history + with pytest.raises(AuditFormatError, match=inventory.baseline_commit) as error: + lineage_api("resolve_current_documents")(repo, replace(inventory, pages_commit=inventory.baseline_commit)) + assert "docs/guides/service/topic-00/index.md" in str(error.value) + + +def test_real_repository_accounts_for_359_files_73_markdown_and_62_documents() -> None: + inventory = load_inventory(MANIFEST) + paths = lineage_api("baseline_paths")(ROOT, inventory) + classified = lineage_api("classify_baseline_files")(ROOT, inventory) + resolved = lineage_api("resolve_current_documents")(ROOT, inventory) + assert len(paths) == len(classified) == 359 + assert sum(path.suffix == ".md" for path in paths) == 73 + assert len(resolved) == len({doc.relative_path for doc in resolved.values()}) == 62 + assert {item.baseline_path for item in classified} == set(paths) + assert {item.status for item in classified} <= {"unchanged", "modified", "renamed", "reviewed"} + assert Counter(item.status for item in classified)["reviewed"] == 6 From c8debdb6d33678cef83ec7d83152f81dc3bc0ad4 Mon Sep 17 00:00:00 2001 From: hellices Date: Sun, 13 Sep 2026 23:33:59 +0900 Subject: [PATCH 04/26] fix(docs): reject ambiguous inventory and shallow history Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/docs/pre_pages.py | 39 ++++++++++- tests/docs/test_pre_pages.py | 124 +++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 1 deletion(-) diff --git a/scripts/docs/pre_pages.py b/scripts/docs/pre_pages.py index 1dfdc9a3..d19a3a05 100644 --- a/scripts/docs/pre_pages.py +++ b/scripts/docs/pre_pages.py @@ -2,6 +2,7 @@ from __future__ import annotations +from collections.abc import Hashable from dataclasses import dataclass from pathlib import Path, PurePosixPath, PureWindowsPath import re @@ -19,6 +20,26 @@ class AuditFormatError(ValueError): """Raised when the inventory or its Git lineage cannot be validated.""" +class _InventorySafeLoader(yaml.SafeLoader): + def construct_mapping(self, node: yaml.MappingNode, deep: bool = False) -> dict: + self.flatten_mapping(node) + keys = set() + for key_node, _ in node.value: + key = self.construct_object(key_node, deep=deep) + if not isinstance(key, Hashable): + raise yaml.constructor.ConstructorError( + "while constructing an inventory mapping", node.start_mark, + "found unhashable key", key_node.start_mark, + ) + if key in keys: + raise yaml.constructor.ConstructorError( + "while constructing an inventory mapping", node.start_mark, + f"duplicate mapping key: {key!r}", key_node.start_mark, + ) + keys.add(key) + return super().construct_mapping(node, deep=deep) + + @dataclass(frozen=True) class BaselineDocument: baseline_path: PurePosixPath @@ -117,7 +138,7 @@ def _reviewed_changes(value: object, label: str) -> Mapping[str, Mapping[str, st def load_inventory(path: Path) -> PrePagesInventory: try: - data = yaml.safe_load(path.read_text(encoding="utf-8")) + data = yaml.load(path.read_text(encoding="utf-8"), Loader=_InventorySafeLoader) except (OSError, UnicodeError, yaml.YAMLError) as error: raise AuditFormatError(f"{path}: cannot read inventory: {error}") from error data = _fields( @@ -173,6 +194,11 @@ def load_inventory(path: Path) -> PrePagesInventory: f"{label}.current_paths must be empty only for excluded-local-state" ) paths = tuple(_relative_path(item, f"{label}.current_paths") for item in current) + seen_paths = set() + for current_path in paths: + if current_path in seen_paths: + raise AuditFormatError(f"{label}: duplicate current_paths entry: {current_path}") + seen_paths.add(current_path) dispositions[baseline_path] = ReviewedDisposition(status, paths, reason) return PrePagesInventory(baseline, pages, similarity, tuple(documents), dispositions) @@ -194,10 +220,19 @@ def _git(repo_root: Path, *arguments: str) -> bytes: def _require_commit(repo_root: Path, commit: str) -> None: _commit_hash(commit, "commit") + _require_full_history(repo_root, commit) if _git(repo_root, "cat-file", "-t", commit).strip() != b"commit": raise AuditFormatError(f"{commit}: expected a Git commit object") +def _require_full_history(repo_root: Path, *commits: str) -> None: + if _git(repo_root, "rev-parse", "--is-shallow-repository").strip() == b"true": + raise AuditFormatError( + f"Shallow Git repository cannot audit required commits: {', '.join(commits)}. " + "Fetch full history before auditing (git fetch --unshallow; checkout fetch-depth: 0)." + ) + + def git_bytes(repo_root: Path, commit: str, path: PurePosixPath) -> bytes: _require_commit(repo_root, commit) path = _relative_path(path.as_posix(), "Git path") @@ -209,6 +244,7 @@ def git_text(repo_root: Path, commit: str, path: PurePosixPath) -> str: def baseline_paths(repo_root: Path, inventory: PrePagesInventory) -> tuple[PurePosixPath, ...]: + _require_full_history(repo_root, inventory.baseline_commit, inventory.pages_commit) _require_commit(repo_root, inventory.baseline_commit) output = _git(repo_root, "ls-tree", "-r", "--name-only", "-z", inventory.baseline_commit) return tuple(PurePosixPath(path) for path in output.decode("utf-8").split("\0") if path) @@ -217,6 +253,7 @@ def baseline_paths(repo_root: Path, inventory: PrePagesInventory) -> tuple[PureP def resolve_current_documents( repo_root: Path, inventory: PrePagesInventory ) -> Mapping[PurePosixPath, Document]: + _require_full_history(repo_root, inventory.baseline_commit, inventory.pages_commit) if len(inventory.documents) != 62: raise AuditFormatError(f"Expected 62 inventory documents, found {len(inventory.documents)}") try: diff --git a/tests/docs/test_pre_pages.py b/tests/docs/test_pre_pages.py index 835cdb1b..5616196a 100644 --- a/tests/docs/test_pre_pages.py +++ b/tests/docs/test_pre_pages.py @@ -244,6 +244,84 @@ def test_inventory_reports_invalid_or_unsafe_yaml(tmp_path: Path, text: str) -> load_inventory(path) +@pytest.fixture +def raw_inventory() -> str: + return f"""version: 1 +baseline_commit: {BASELINE} +pages_commit: {PAGES} +rename_similarity: 20 +documents: + - baseline_path: old/guide.md + pages_path: guides/service/topic/index.md + reviewed_changes: + prose: + fingerprint: Preserved in the sample README. +dispositions: + old/README.md: + status: replaced-summary + current_paths: + - docs/services/service/topic/index.md + reason: Replaced by the canonical overview. +""" + + +@pytest.mark.parametrize( + ("original", "replacement", "key"), + [ + ("version: 1", "version: 2\nversion: 1", "version"), + ( + " - baseline_path: old/guide.md", + " - baseline_path: ignored.md\n baseline_path: old/guide.md", + "baseline_path", + ), + ( + " prose:\n", + " prose: {}\n prose:\n", + "prose", + ), + ( + " fingerprint: Preserved in the sample README.", + " fingerprint: Lost reason.\n fingerprint: Preserved in the sample README.", + "fingerprint", + ), + ( + " old/README.md:\n", + " old/README.md:\n status: invalid\n old/README.md:\n", + "old/README.md", + ), + ( + " status: replaced-summary", + " status: invalid\n status: replaced-summary", + "status", + ), + ], +) +def test_inventory_rejects_raw_duplicate_mapping_keys_at_every_level( + tmp_path: Path, raw_inventory: str, original: str, replacement: str, key: str +) -> None: + path = tmp_path / "duplicate-mapping.yml" + assert original in raw_inventory + path.write_text(raw_inventory.replace(original, replacement), encoding="utf-8") + with pytest.raises(AuditFormatError, match="duplicate") as error: + load_inventory(path) + assert str(path) in str(error.value) + assert key in str(error.value) + + +@pytest.mark.parametrize("status", ["replaced-summary", "replaced-test"]) +def test_inventory_rejects_raw_duplicate_current_paths( + tmp_path: Path, raw_inventory: str, status: str +) -> None: + path = tmp_path / "duplicate-current-paths.yml" + current = " - docs/services/service/topic/index.md\n" + text = raw_inventory.replace("status: replaced-summary", f"status: {status}") + path.write_text(text.replace(current, current + current), encoding="utf-8") + with pytest.raises(AuditFormatError, match="duplicate.*current_paths") as error: + load_inventory(path) + assert "old/README.md" in str(error.value) + assert "docs/services/service/topic/index.md" in str(error.value) + + def test_git_readers_preserve_text_and_binary(history: tuple[Path, str, str]) -> None: repo, baseline, _ = history assert git_text(repo, baseline, PurePosixPath("한글 문서.md")) == "첫 문장\r\n둘째 문장\n" @@ -626,3 +704,49 @@ def test_real_repository_accounts_for_359_files_73_markdown_and_62_documents() - assert {item.baseline_path for item in classified} == set(paths) assert {item.status for item in classified} <= {"unchanged", "modified", "renamed", "reviewed"} assert Counter(item.status for item in classified)["reviewed"] == 6 + + +def shallow_clone_with_anchors( + tmp_path: Path, repo: Path, inventory: PrePagesInventory, depth: int +) -> Path: + shallow = tmp_path / "shallow-with-anchors" + git(tmp_path, "clone", "-q", f"--depth={depth}", repo.as_uri(), str(shallow)) + assert git(shallow, "rev-parse", "--is-shallow-repository").strip() == b"true" + assert int(git(shallow, "rev-list", "--count", "HEAD")) == depth + assert int(git(repo, "rev-list", "--count", "HEAD")) > depth + for anchor in (inventory.baseline_commit, inventory.pages_commit): + assert git(shallow, "cat-file", "-t", anchor).strip() == b"commit" + return shallow + + +@pytest.mark.parametrize("name", ["git_bytes", "git_text", "baseline_paths", "classify_baseline_files"]) +def test_git_apis_reject_shallow_boundary_even_with_both_anchors_present( + tmp_path: Path, file_history: tuple[Path, PrePagesInventory], name: str +) -> None: + repo, inventory = file_history + shallow = shallow_clone_with_anchors(tmp_path, repo, inventory, depth=2) + with pytest.raises(AuditFormatError, match="(?i)shallow") as error: + if name in {"git_bytes", "git_text"}: + lineage_api(name)(shallow, inventory.baseline_commit, PurePosixPath("한글 문서.md")) + else: + lineage_api(name)(shallow, inventory) + message = str(error.value) + assert inventory.baseline_commit in message + if name not in {"git_bytes", "git_text"}: + assert inventory.pages_commit in message + assert "fetch" in message.lower() + assert "full history" in message.lower() + + +def test_document_resolution_rejects_shallow_boundary_with_both_anchors_present( + tmp_path: Path, document_history: tuple[Path, PrePagesInventory] +) -> None: + repo, inventory = document_history + shallow = shallow_clone_with_anchors(tmp_path, repo, inventory, depth=3) + with pytest.raises(AuditFormatError, match="(?i)shallow") as error: + lineage_api("resolve_current_documents")(shallow, inventory) + message = str(error.value) + assert inventory.baseline_commit in message + assert inventory.pages_commit in message + assert "fetch" in message.lower() + assert "full history" in message.lower() From b1ce99afb8dd0b192ef7c7e84fac30f6f4bf95e7 Mon Sep 17 00:00:00 2001 From: hellices Date: Mon, 14 Sep 2026 00:09:38 +0900 Subject: [PATCH 05/26] test(docs): verify pre-Pages content preservation Compare normalized source structures against the fixed historical baseline, with exact reviewed fingerprints rather than similarity approvals. Preserve the omitted Agent Memory reader/audience map as linked historical sample evidence. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../microsoft-foundry/agent-memory/index.md | 4 + .../samples/research-artifacts/README.md | 19 + scripts/docs/pre_pages.py | 48 +- scripts/docs/pre_pages_content.py | 374 +++++ scripts/docs/pre_pages_inventory.yml | 1410 ++++++++++++++++- tests/docs/test_pre_pages.py | 14 +- tests/docs/test_pre_pages_content.py | 355 +++++ 7 files changed, 2176 insertions(+), 48 deletions(-) create mode 100644 scripts/docs/pre_pages_content.py create mode 100644 tests/docs/test_pre_pages_content.py diff --git a/docs/services/microsoft-foundry/agent-memory/index.md b/docs/services/microsoft-foundry/agent-memory/index.md index 44a8276e..4797c77a 100644 --- a/docs/services/microsoft-foundry/agent-memory/index.md +++ b/docs/services/microsoft-foundry/agent-memory/index.md @@ -53,6 +53,10 @@ redirect_from: ## 목적별 빠른 경로 +이관 전 문서별 상세 읽기 목표와 대상 독자는 +[원문 독자 지도](https://github.com/hellices/devguidesample/blob/main/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md#historical-reader-map-pre-pages)에 +역사적 기록으로 보존한다. + **시간이 없다면** → [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 index 25294a31..4049c787 100644 --- a/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md +++ b/docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md @@ -3,3 +3,22 @@ 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. + +## Historical reader map (pre-Pages) + +The pre-Pages snapshot `a4e680116db1016a698e64baae9efde858a8eafa` +included the following detailed reading objectives and audiences. The topic's +generated navigation replaced the old document list, but did not retain these +audience assignments. This is a historical research map, not a current product-support statement. +Links now point to the corresponding canonical documents. + +### 문서 구성 + +| 문서 | 내용 | 대상 | +|------|------|------| +| **[01. 메모리 분류 체계](../../taxonomy/index.md)** | Agent Memory **8유형 통합 정의**, 유형별 **저장소 선택지**와 Azure 매핑, Retention 정책, 30기법 6패밀리 지도 | 전원 | +| **[02. 아키텍처 패턴](../../architecture-patterns/index.md)** | Single-Store / Dual-Store / Tiered / Graph-Augmented / Full Cognitive 5패턴, 비교표, 선택 결정 트리 | 아키텍트 | +| **[03. 파이프라인과 검색](../../pipeline-retrieval/index.md)** | 쓰기 경로(추출→중복·모순 해소→라우팅) / 읽기 경로(hybrid search→RRF→rerank→MMR→temporal), 단계별 지연 비용 | 개발자 | +| **[04. 프레임워크 비교](../../frameworks/index.md)** | Mem0 · Zep · Graphiti · Letta(MemGPT) · Cognee · Azure AI Search, **자체 구현 vs 도입 판단 기준** | 의사결정자 | +| **[05. 프로덕션과 평가](../../production-evaluation/index.md)** | Tiered storage, PII·GDPR, TTL·샤딩·관측성·비용, 평가 지표, LoCoMo/LongMemEval 벤치마크 | SRE / 개발자 | +| **[06. 커머스 적용 설계](../../commerce/index.md)** | **메모리 스키마 · 지연 예산 · 개인화 강도 단계화 · 안티패턴 · Phase 0~4 로드맵 · 기술+비즈니스 지표** | 전원 (핵심) | diff --git a/scripts/docs/pre_pages.py b/scripts/docs/pre_pages.py index d19a3a05..3996337b 100644 --- a/scripts/docs/pre_pages.py +++ b/scripts/docs/pre_pages.py @@ -123,16 +123,48 @@ def _relative_path(value: object, label: str) -> PurePosixPath: return path -def _reviewed_changes(value: object, label: str) -> Mapping[str, Mapping[str, str]]: - if not isinstance(value, dict): +STRUCTURE_CATEGORIES = ( + "title", "headings", "prose", "code", "tables", "images", "local_link_labels", +) + + +def validate_reviewed_changes( + value: object, label: str = "reviewed_changes", +) -> Mapping[str, Mapping[str, str]]: + """Validate exact source fingerprints and actionable, path-specific review reasons.""" + if not isinstance(value, Mapping): raise AuditFormatError(f"{label} must be a mapping") for category, reasons in value.items(): - _nonempty_string(category, label) - if not isinstance(reasons, dict): + if category not in STRUCTURE_CATEGORIES: + raise AuditFormatError(f"{label}: unknown structure category: {category!r}") + if not isinstance(reasons, Mapping): raise AuditFormatError(f"{label}.{category} must be a mapping") for fingerprint, reason in reasons.items(): - _nonempty_string(fingerprint, label) - _nonempty_string(reason, label) + if not isinstance(fingerprint, str) or not re.fullmatch(r"[0-9a-f]{64}", fingerprint): + raise AuditFormatError(f"{label}.{category}: fingerprint must be lowercase SHA-256") + reason_label = f"{label}.{category}.{fingerprint}.reason" + _nonempty_string(reason, reason_label) + if len(reason.strip()) < 40 or not re.search( + r"(?:docs|samples)/[^\s`]+" + r"|\bsection\s+[`\"“][^`\"”]+[`\"”]" + r"|\bsafety:\s+.{25,}", + reason, + re.IGNORECASE, + ): + raise AuditFormatError( + f"{reason_label} must name a specific replacement path, quoted section, " + "or explicit safety: transformation, not a generic approval" + ) + if not re.search( + r"\b(?:preserv\w*|replac\w*|restor\w*|retain\w*|remain\w*|unchanged|" + r"redact\w*|safety|renam\w*|remov\w*|migrat\w*)\b" + r"|보존|대체|복원|유지|비식별|이관|치환|삭제", + reason, + re.IGNORECASE, + ): + raise AuditFormatError( + f"{reason_label} must explain preservation, replacement, or safety, not just 'updated'" + ) return value @@ -170,7 +202,9 @@ def load_inventory(path: Path) -> PrePagesInventory: paths[field] = value documents.append(BaselineDocument( **paths, - reviewed_changes=_reviewed_changes(raw.get("reviewed_changes", {}), f"{label}.reviewed_changes"), + reviewed_changes=validate_reviewed_changes( + raw.get("reviewed_changes", {}), f"{label}.reviewed_changes", + ), )) if not isinstance(data["dispositions"], dict): diff --git a/scripts/docs/pre_pages_content.py b/scripts/docs/pre_pages_content.py new file mode 100644 index 00000000..f14781b1 --- /dev/null +++ b/scripts/docs/pre_pages_content.py @@ -0,0 +1,374 @@ +"""Source-structure preservation against the fixed pre-Pages Git baseline.""" + +from __future__ import annotations + +from collections import Counter +from dataclasses import asdict, dataclass +from difflib import SequenceMatcher +from hashlib import sha256 +from html import unescape +import json +from pathlib import Path, PurePosixPath +import re +from typing import Mapping +import unicodedata +from urllib.parse import unquote, urlsplit + +from scripts.docs.pre_pages import ( + AuditFormatError, + PrePagesInventory, + STRUCTURE_CATEGORIES, + git_text, + resolve_current_documents, + validate_reviewed_changes, +) + + +@dataclass(frozen=True) +class MarkdownStructure: + title: str + headings: tuple[str, ...] + prose: tuple[str, ...] + code: tuple[str, ...] + tables: tuple[str, ...] + images: tuple[str, ...] + local_link_labels: tuple[str, ...] + + +@dataclass(frozen=True) +class StructureFinding: + category: str + fingerprint: str + excerpt: str + + +@dataclass(frozen=True) +class DocumentPreservation: + baseline_path: PurePosixPath + current_path: PurePosixPath + status: str + text_similarity: float + missing: tuple[StructureFinding, ...] + reviewed: tuple[StructureFinding, ...] + + +_DESTINATION = r'(<[^>\n]*>|(?:\\.|[^\s()\\]|\([^()\n]*\))*)' +_LINK_END = r'''(?:\s+(?:"[^"]*"|'[^']*'|\([^)]*\)))?\s*\)''' +_LINK = re.compile( + r'(!?)\[([^\[\]]*(?:\[[^\[\]]*\][^\[\]]*)*)\]\(\s*' + _DESTINATION + + _LINK_END +) +_IMAGE = re.compile(r'!\[([^\[\]]*)\]\(\s*' + _DESTINATION + _LINK_END) +_REFERENCE = re.compile(r"(!?)\[([^\[\]]+)\](?:\[([^\[\]]*)\])?") +_DEFINITION = re.compile(r"(?m)^ {0,3}\[([^\]]+)\]:\s*" + _DESTINATION + r"[^\n]*$") +_ATTRIBUTE = re.compile(r'''([\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))''') +_WRAPPER = re.compile( + r"]*>", + re.IGNORECASE, +) + + +def _space(text: str) -> str: + return " ".join(text.split()) + + +def _segments(text: str) -> list[tuple[str | None, str]]: + """Separate fences before applying any prose-only normalization.""" + segments = [] + normal: list[str] = [] + code: list[str] = [] + fence = "" + language = "" + indent = "" + comment = False + for line in text.split("\n"): + if fence: + if re.fullmatch(r"\s*" + re.escape(fence[0]) + "{" + str(len(fence)) + r",}\s*", line): + segments.append((language, "\n".join(code).strip("\n"))) + fence, code = "", [] + else: + code.append(line.removeprefix(indent).rstrip()) + continue + visible = [] + offset = 0 + while offset < len(line): + if comment: + end = line.find("-->", offset) + if end < 0: + break + offset, comment = end + 3, False + continue + token = re.search(r"(`+).*?\1||"DNS Query"| RESOLVER["systemd-resolved
127.0.0.53"] + RESOLVER -->''.' + 4cc41bf5a9cd2b01eaefbd95bd337c47e7dbec1543ff325a21fdb7ab4eda69b3: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current code replacement begins: + ''# PRD VNet에 링크 추가 az network private-dns link vnet create \\ --name "link-to-prd-vnet" \\ --zone-name + "" \\ --resource-group ". Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''AKS 클러스터 | aks-example-koreacentral-01 (Private Cluster)''.' + bfd684c367e972b3460eb62a456f363c6bdbc785c002e59488aebda2284ba4a5: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''리소스 그룹 | rg-example-koreacentral-01''.' + 94c21b10dbe667b9f47b937912e7d995de72f5526d5e6c2802e25ad697f8dcce: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''PRD 리소스 그룹 | rg-example-koreacentral-prd-01''.' + 995158c790ba9fe3ee7f8a3232d66e14a647bffa46e7b8a5c6b7f20f1c746c72: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''Public IP | pip-fw-example-koreacentral-prd-01 | Standard SKU''.' + baacdbdcd3a0b86ce62dc3ecdaecb62c91b4f07f4f4db8154a961e75134e8286: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''VNet (기존) | vnet-example-koreacentral-01 (10.0.0.0/10)''.' + 60afb1c68779b99db7b65dff72dde0a4b3e66377f7579ac6e2a29b0d54a644ab: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''VNet (PRD) | vnet-example-koreacentral-prd-01 (10.100.0.0/16)''.' + ed16ab3e60e9fb812d6e89188b1346f14bc8c49a551d0665ff6312d60e05d336: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''Azure Firewall | fw-example-koreacentral-prd-01 (Private IP: 10.100.0.4)''.' + ddcefb2d91cc9d847a48657cdd22c75238df1e700e3957df9e7f7b0157e1ed7d: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''Azure Monitor Workspace | azuremonitor-workspace-example-krc-01''.' + 39224c38099b50425f28ad69fbe812a72684f6acb879bbbea236467626b5bc32: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''Managed Grafana | grafanaworkspaceexample''.' + d97e332b81cde3eba71a3802133c3cee677171ad8ad226385c783f33ff0b6f84: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''privatelink.monitor.azure.com | 메트릭 수집 엔드포인트 | vnet-example-koreacentral-prd-01''.' + b1bdcfb814366ed3a685131abb36a7a106f8723864d4d58a2e3d693cc5e69a4c: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''privatelink.ods.opinsights.azure.com | ODS 데이터 채널 | vnet-example-koreacentral-prd-01''.' + fbf53873149396c6970488dc818e37574b77531268e0ddf1c868f95f451ebd1d: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''privatelink.oms.opinsights.azure.com | OMS 포털 | vnet-example-koreacentral-prd-01''.' + a023b598ecac63bcd02648fef1a4c89bed7d2a4f2de51f4426dae8607ce0c2f1: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''privatelink.agentsvc.azure-automation.net | 에이전트 서비스 | vnet-example-koreacentral-prd-01''.' + 5ab80b1cde1ccc1cdc2d55d0034dda565fc8dcc1595d214c48cded114bbcb27a: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''privatelink.blob.core.windows.net | Blob 스토리지 | vnet-example-koreacentral-prd-01''.' + 4171198b18e12970a4d725b2ed7dfac43555ace5cc0aef041dd6dc9a725f9562: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''*.privatelink.koreacentral.azmk8s.io | AKS API Server | vnet-example-koreacentral-prd-01''.' + f59b5f0be1612339205a66b664559cf857a99375037daf859709b6efd7cd8d73: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''privatelink.redis.azure.net | Redis Cache | vnet-example-koreacentral-prd-01''.' + 933b1376898a2ead7b68157deb139af9a3f5101c60da5470896ae300bf07c27b: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''MSProm-koreacentral-aks-example-koreacentral-01 | rg-example-koreacentral-01 | MSProm-koreacentral-aks-example-koreacentral''.' + e545d145796957437bcbea7610d0e6f25f691fa2c07cbc63225a5d9e14122059: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''azuremonitor-workspace-example-krc-01 | MA_..._managed | azuremonitor-workspace-example-krc-01''.' + 6fcf8f81ea35d9515ed12cf8a5c242e7fa802cc887e79d2aacc43416ba0db048: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''configurationAccessEndpoint | N/A | MSProm-koreacentral-aks-example-koreacentral ✅''.' + 68b32234335d9a9ef7eadded9bfda2eb6768b72aa23e0a92979b92a546e65061: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''Resource Group | rg-example-koreacentral-prd-01 | Korea Central''.' + 791178e78ff10c8a4e214cbdbf680ece83bcfc93724ac7da171bf5ab1211ccfb: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''VNet | vnet-example-koreacentral-prd-01 | 10.100.0.0/16''.' + 06000515a008f2a2917bb02de0e4c0f686dbaeeefd02c3bbc5973fbc07c2987a: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''Azure Firewall | fw-example-koreacentral-prd-01 | Standard SKU, DNS Proxy 활성화''.' + e314e69e39f83a68b872625644fafa45e23c027f2feacc8c808f6c285a0b7798: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: + Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples + now use the example resource namespace and . Both az rest configurationAccessEndpoint + requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement begins: + ''Public IP | pip-fw-example-koreacentral-prd-01 | Standard SKU''.' - baseline_path: monitor/sre-agent-event-lab/README.md pages_path: labs/azure-monitor/sre-agent-event-lab/index.md - reviewed_changes: {} + reviewed_changes: + prose: + 1c3a39700c2a8d219e620296cdc608dda909d0077f62895183cd2f5313544f14: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: + The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state + gates, cost warning, scoring and cleanup remain. Current prose replacement begins: ''포털에서만 할 수 있는 + 설정이 남아 있습니다. 저장소 연결, 지식 문서 업로드, Azure Monitor incident platform 연결, Review 모드 응답 계획, 역할 할당을 에이전트 + 설정이 순서대로 안내합니다.''.' + a2ad55106e8c2cb6f8391a41cadf665e3517675e3ff39d75ae5b57d1d1315db6: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: + The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state + gates, cost warning, scoring and cleanup remain. Current prose replacement begins: ''기본 실습에는 Logic + App bridge를 배포하지 않습니다. 제품 표준 경로는 Azure Monitor를 incident platform으로 연결하는 것이고, 예전 실측에서 쓰던 Action + Group + Logic App 인증 경로는 레거시 기''.' + 2ca86442294e2f99044c23fc5623c201686c9332f032e3d1253159630704d4e1: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: + The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state + gates, cost warning, scoring and cleanup remain. Current prose replacement begins: ''장애를 주입하기 전에 + 정상 부하가 Application Insights까지 도달하는지 확인하고, 그 사실을 기록해야 S1이 열립니다. 텔레메트리가 없는 워크로드에 장애를 넣으면 주입한 장애와 원래부터 + 안 보이던 상태를 구별할 수 없습니다. 명령은 에이''.' + f0a4ebba54d5beb20034578ede022134d9baa6047ad234c0223b2ccb6d014fa4: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: + The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state + gates, cost warning, scoring and cleanup remain. Current prose replacement begins: ''채점 기준, 사람이 + 채워야 하는 판정, 종합 판정 해석은 결과 채점에 있습니다.''.' + 33621ea5336f4b63bc4bffb679ec0aa3630e7dad73c975d29348530fdad07bf8: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: + The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state + gates, cost warning, scoring and cleanup remain. Current prose replacement begins: ''정적 임계값 대신 Dynamic + Threshold로 확장하는 설계는 Dynamic Thresholds 확장 설계에 정리해 두었습니다.''.' + code: + 6faff368410a9e4c46cfeda44df7b9d874c51daf2930ac47df1578133c4775b1: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: + The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state + gates, cost warning, scoring and cleanup remain. Current code replacement begins: ''az login --use-device-code + azd auth login source ./docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/lab-env.sh''.' + 7e2247eec243f924ffd1b30a2b412b3022389dfcbf3fba3b3cf5dc7d2f80e4c6: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: + The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state + gates, cost warning, scoring and cleanup remain. Current code replacement begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab''.' + tables: + ffa178128112fd99f8d40cc82f4fd9fce361fbc8e3605f534aa3a0de7f44c89c: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: + The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state + gates, cost warning, scoring and cleanup remain. Current tables replacement begins: ''S1 | HTTP + 500 응답 | S1 — HTTP 500 장애''.' + b46d11954a35fe0afd5b627882274a683e78a850a5e724008791321d3a8aeb16: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: + The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state + gates, cost warning, scoring and cleanup remain. Current tables replacement begins: ''S2 | 주문 API + 지연 | S2 — 응답 지연''.' + 8b80f1ae4540feb17783ea900ad47f7e92cc74782a14e736d717219e45b327b6: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: + The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state + gates, cost warning, scoring and cleanup remain. Current tables replacement begins: ''S3 | Blob + 읽기 권한 제거 | S3 — Blob 권한 장애''.' + cc08d785e8dd9c540bb261bc5c1d481559a041087bc1d7a2f317b6dcb889d762: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: + The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state + gates, cost warning, scoring and cleanup remain. Current tables replacement begins: ''Agent가 스레드를 + 만들지 않음 | 에이전트 설정의 incident platform·응답 계획 확인''.' + 4c29553b4450e5b44b6e77e81286bd106bd9b811288a1cd6f78be94fc740bc5f: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: + The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state + gates, cost warning, scoring and cleanup remain. Current tables replacement begins: ''채점이 INCOMPLETE + | 결과 채점의 수동 판정 절''.' + local_link_labels: + 22ebccef48adf3797ef14e253543c95913b641f5e0ac0482c5ab31694b8f74a0: Retained label '.devcontainer/README.md' + in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, pointing to .devcontainer/README.md. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + 44028050825bcfa642b14ff4262d4da0b9393687cf19137c36484aa09ff58e8b: Replaced retired path label with + '에이전트 설정' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/setup/index.md. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + 9e7735ab56a3c5447d3e3fced04c1b2fe3a2992cf3180f9ca2b688e7fee0ea64: Replaced retired path label with + '실측 검증 결과' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/validation-results/index.md. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + 721d882d49a3cd517405ccc227fe908e4a862088f67bfde974795095f05c02b1: Replaced retired path label with + 'S1 — HTTP 500 장애' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, pointing to + docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md. The current canonical source + or owning-sample target was checked; this is a source-link/reader-label migration, not removal of + the referenced content. + 5c8bc528ec7649959420de6f956d39d8fe06deebea6a99112373148f0d0e814c: Replaced retired path label with + 'S2 — 응답 지연' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + 1898c96d3ba8c474502f1860ec20522ea2883d180b44f744e7b6812908fba571: Replaced retired path label with + 'S3 — Blob 권한 장애' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, pointing to + docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md. The current canonical + source or owning-sample target was checked; this is a source-link/reader-label migration, not removal + of the referenced content. + d0352a09849221675c8996d4f85514fa1067158f15ca0a74d3747d499c347b55: Replaced retired path label with + '결과 채점' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/results/index.md. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + b342b4cce7c7903c2a29215e5442ef0eb9d9e5d166fbb3a3a5a1a8a280f6d399: Replaced retired path label with + 'Dynamic Thresholds 확장 설계' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, pointing + to docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md. The current canonical + source or owning-sample target was checked; this is a source-link/reader-label migration, not removal + of the referenced content. - baseline_path: monitor/sre-agent-event-lab/dynamic-thresholds.md pages_path: guides/azure-monitor/sre-agent-dynamic-thresholds/index.md - reviewed_changes: {} + reviewed_changes: + prose: + 014a10d01aa0e5fb54547492a3e2a67766e36db7efa6f0866894dba44914cdb5: 'In docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md: + The legacy Event path note still dates the Action Group/Logic App bridge to 2026-08-12 and says + it is not deployed by the current lab. Its reference is now 실측 검증 결과 at docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + rather than the retired filename. Current prose replacement begins: ''- Event path(레거시 bridge): + Dynamic alert → Action Group → Logic App managed identity → SRE HTTP Trigger → Review-mode investigation. + 2026-08-''.' + local_link_labels: + 9e7735ab56a3c5447d3e3fced04c1b2fe3a2992cf3180f9ca2b688e7fee0ea64: Replaced retired path label with + '실측 검증 결과' in docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md, pointing + to docs/services/azure-monitor/azure-sre-agent/validation-results/index.md. The current canonical + source or owning-sample target was checked; this is a source-link/reader-label migration, not removal + of the referenced content. - baseline_path: monitor/sre-agent-event-lab/guides/01-agent-setup.md pages_path: labs/azure-monitor/sre-agent-event-lab-setup/index.md - reviewed_changes: {} + reviewed_changes: + prose: + 24d42fb71e135de7ab48b81149ddb9e357fbe50028dd7fe668cb587d4725e108: 'In docs/services/azure-monitor/azure-sre-agent/setup/index.md: + The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + The Knowledge files row names the canonical incident-runbook and the next-step link names scenario-http-500. + The required roles, Review mode and baseline/setup state acknowledgements remain. Current prose + replacement begins: ''첫 장애를 주입합니다: S1 — HTTP 500 장애''.' + code: + b052995a546e9e1f9ae8a5ab4d9dfd8f2dcbfb5e46c85107831eee8f5cb45f8e: 'In docs/services/azure-monitor/azure-sre-agent/setup/index.md: + The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + The Knowledge files row names the canonical incident-runbook and the next-step link names scenario-http-500. + The required roles, Review mode and baseline/setup state acknowledgements remain. Current code replacement + begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab source ./scripts/lab-env.sh''.' + tables: + 3753279e936bb5b6e363f980fdbdec9b249ca829952b0bfff6a972580d0c6f1e: 'In docs/services/azure-monitor/azure-sre-agent/setup/index.md: + The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + The Knowledge files row names the canonical incident-runbook and the next-step link names scenario-http-500. + The required roles, Review mode and baseline/setup state acknowledgements remain. Current tables + replacement begins: ''Knowledge files | 인시던트 대응 런북 | 조사 순서와 금지 사항을 팀 규칙으로 강제합니다''.' + local_link_labels: + 673435c83fe369747f909ad329e995ff4aab440b39b30b0967fb7c937a670950: Replaced retired path label with + '인시던트 대응 런북' in docs/services/azure-monitor/azure-sre-agent/setup/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/incident-runbook/index.md. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + 1c3dbf23991386b472cb4d1276a30837f5c72ce8a85a554953faff7c54e156d3: Replaced retired path label with + 'S1 — HTTP 500 장애' in docs/services/azure-monitor/azure-sre-agent/setup/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. - baseline_path: monitor/sre-agent-event-lab/guides/02-scenario-s1.md pages_path: labs/azure-monitor/sre-agent-scenario-http-500/index.md - reviewed_changes: {} + reviewed_changes: + prose: + d0949831998e46a635c7028c64235af4300314bbe05fe974936b14e693d013d7: 'In docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md: + The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Start-condition and missing-thread advice point to setup, scoring points to results, and the next + step points to scenario-latency using reader-facing labels. HTTP 500 injection, state gates, capture + statuses and recovery commands remain. Current prose replacement begins: ''- 에이전트 설정을 마쳤고 evidence/state.json에 + baseline_passed와 agent_setup_acknowledged가 기록되어 있습니다.''.' + 7a6cc00434bf3af476835bccced55cbbde8de49ad9c89040d54cbf91981667f1: 'In docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md: + The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Start-condition and missing-thread advice point to setup, scoring points to results, and the next + step points to scenario-latency using reader-facing labels. HTTP 500 injection, state gates, capture + statuses and recovery commands remain. Current prose replacement begins: ''성공은 conclusion 하나뿐입니다. + 부분 성공은 결론이 나왔지만 내용이 얕은 경우이며, 이때도 상태는 conclusion이고 점수는 결과 채점에서 갈립니다. 빈 성공 화면을 만들지 않고 누락 상태와 마지막 확인 + 시각을 그대로 그림에 남깁니다.''.' + 602ac0a5e1349c2f535a8ead51ad2cc1a399a7531214aca08c336408060609d1: 'In docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md: + The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Start-condition and missing-thread advice point to setup, scoring points to results, and the next + step points to scenario-latency using reader-facing labels. HTTP 500 injection, state gates, capture + statuses and recovery commands remain. Current prose replacement begins: ''thread-not-created가 나오면 + 에이전트 설정의 incident platform과 응답 계획부터 다시 확인합니다.''.' + aeb020ec3047ebb43cecf025f3d2e755d82055561281b915692982956fbe9079: 'In docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md: + The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Start-condition and missing-thread advice point to setup, scoring points to results, and the next + step points to scenario-latency using reader-facing labels. HTTP 500 injection, state gates, capture + statuses and recovery commands remain. Current prose replacement begins: ''지연 장애로 넘어갑니다: S2 — 응답 + 지연''.' + code: + b052995a546e9e1f9ae8a5ab4d9dfd8f2dcbfb5e46c85107831eee8f5cb45f8e: 'In docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md: + The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Start-condition and missing-thread advice point to setup, scoring points to results, and the next + step points to scenario-latency using reader-facing labels. HTTP 500 injection, state gates, capture + statuses and recovery commands remain. Current code replacement begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab + source ./scripts/lab-env.sh''.' + local_link_labels: + 220ab3d6bd9f5bcf6b432aee6496d3d33dfc04f52e3166900819d49d0a33660f: Replaced retired path label with + '에이전트 설정' in docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md, pointing to + docs/services/azure-monitor/azure-sre-agent/setup/index.md. The current canonical source or owning-sample + target was checked; this is a source-link/reader-label migration, not removal of the referenced + content. + 642002bebe57cfb2d5d3168236cb52f8f261354812320f7c408f833079661570: Replaced retired path label with + '결과 채점' in docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/results/index.md. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + 2c64e239550534f5e89f6403e8338cac2756d397dfbbce90d29af73440d01992: Replaced retired path label with + 'S2 — 응답 지연' in docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md, pointing + to docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md. The current canonical + source or owning-sample target was checked; this is a source-link/reader-label migration, not removal + of the referenced content. - baseline_path: monitor/sre-agent-event-lab/guides/03-scenario-s2.md pages_path: labs/azure-monitor/sre-agent-scenario-latency/index.md - reviewed_changes: {} + reviewed_changes: + prose: + e4464baf5ec71e4bcbb043edc8a52d8e98d3fa260afffda990816f88c81a3ed7: 'In docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md: + The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + The prerequisites and next-step prose now name scenario-http-500 and scenario-blob-permission. S1 + recovery requirements, latency fault, conclusion capture gate and recovery commands remain. Current + prose replacement begins: ''- S1 — HTTP 500 장애의 S1이 복구되고 캡처가 conclusion으로 끝났습니다.''.' + c0da5ea550501e006069b2ce322ed03f38ad31fbe447ba4559cbda587b4162a3: 'In docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md: + The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + The prerequisites and next-step prose now name scenario-http-500 and scenario-blob-permission. S1 + recovery requirements, latency fault, conclusion capture gate and recovery commands remain. Current + prose replacement begins: ''권한 장애로 넘어갑니다: S3 — Blob 권한 장애''.' + code: + b052995a546e9e1f9ae8a5ab4d9dfd8f2dcbfb5e46c85107831eee8f5cb45f8e: 'In docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md: + The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + The prerequisites and next-step prose now name scenario-http-500 and scenario-blob-permission. S1 + recovery requirements, latency fault, conclusion capture gate and recovery commands remain. Current + code replacement begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab source + ./scripts/lab-env.sh''.' + local_link_labels: + 1c3dbf23991386b472cb4d1276a30837f5c72ce8a85a554953faff7c54e156d3: Replaced retired path label with + 'S1 — HTTP 500 장애' in docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md, pointing + to docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md. The current canonical + source or owning-sample target was checked; this is a source-link/reader-label migration, not removal + of the referenced content. + 7d81587b0c8906bf8afea33f1abca69106523b68c0d0cd96c1371902e466f702: Replaced retired path label with + 'S3 — Blob 권한 장애' in docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md, pointing + to docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md. The current canonical + source or owning-sample target was checked; this is a source-link/reader-label migration, not removal + of the referenced content. - baseline_path: monitor/sre-agent-event-lab/guides/04-scenario-s3.md pages_path: labs/azure-monitor/sre-agent-scenario-blob-permission/index.md - reviewed_changes: {} + reviewed_changes: + prose: + 3980a12d12b3d02cd5d1d39a9dbb621bb87a8e5719bb8496cf084a074b1cbe22: 'In docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md: + The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Prerequisite and next-step prose now name scenario-latency and results. Blob-role removal, S2 recovery/capture + gates and least-privilege restoration evidence remain. Current prose replacement begins: ''- S2 + — 응답 지연의 S2가 복구되고 캡처가 conclusion으로 끝났습니다.''.' + 8ffa7b1113e65afce2b1049c2ae54853457120e34024b5e7358d7a96eb991c68: 'In docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md: + The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Prerequisite and next-step prose now name scenario-latency and results. Blob-role removal, S2 recovery/capture + gates and least-privilege restoration evidence remain. Current prose replacement begins: ''수집한 근거를 + 채점합니다: 결과 채점''.' + code: + b052995a546e9e1f9ae8a5ab4d9dfd8f2dcbfb5e46c85107831eee8f5cb45f8e: 'In docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md: + The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Prerequisite and next-step prose now name scenario-latency and results. Blob-role removal, S2 recovery/capture + gates and least-privilege restoration evidence remain. Current code replacement begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab + source ./scripts/lab-env.sh''.' + local_link_labels: + 2c64e239550534f5e89f6403e8338cac2756d397dfbbce90d29af73440d01992: Replaced retired path label with + 'S2 — 응답 지연' in docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md, pointing + to docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md. The current canonical + source or owning-sample target was checked; this is a source-link/reader-label migration, not removal + of the referenced content. + 642002bebe57cfb2d5d3168236cb52f8f261354812320f7c408f833079661570: Replaced retired path label with + '결과 채점' in docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md, pointing + to docs/services/azure-monitor/azure-sre-agent/results/index.md. The current canonical source or + owning-sample target was checked; this is a source-link/reader-label migration, not removal of the + referenced content. - baseline_path: monitor/sre-agent-event-lab/guides/05-results.md pages_path: labs/azure-monitor/sre-agent-results/index.md - reviewed_changes: {} + reviewed_changes: + code: + 67a6a1cd3fa2470fa487d0030975e2dcc8399fe8015dc21d9d02e112b6c00afe: 'In docs/services/azure-monitor/azure-sre-agent/results/index.md: + The scoring fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab before + invoking the unchanged app/.venv/bin/python scripts/score.py --evidence-root evidence command. Current + code replacement begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab app/.venv/bin/python + scripts/score.py --evidence-root evidence''.' - baseline_path: monitor/sre-agent-event-lab/runbooks/incident-response.md pages_path: guides/azure-monitor/sre-agent-incident-runbook/index.md reviewed_changes: {} - baseline_path: monitor/sre-agent-event-lab/validation-results.md pages_path: research/azure-monitor/sre-agent-validation-results/index.md - reviewed_changes: {} + reviewed_changes: + prose: + 8271d6dbca2ffc2138a6910a441d84bf389105cd37f8657345b5645f9a8e918b: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: + Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured + outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example + names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. + Current execution instructions are explicitly linked to event-lab/index.md. Current prose replacement + begins: ''제품 개요와 실사용 패턴은 Azure SRE Agent 소개 자료를 먼저 참고한다. 이 문서는 S1/S2/S3 시나리오에서 측정한 수치, timeline, + evidence, 한계를 정리한다. 기록 시점 주의. 아래 결과는 azd 재구성 이전에 손으로 ''.' + 59eaa5e71ada1c5c2e7aa1c3f44779c0e9ced379a85d4255c9a6e91abdc1c509: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: + Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured + outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example + names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. + Current execution instructions are explicitly linked to event-lab/index.md. Current prose replacement + begins: ''- Agent thread: ''.' + 67c696e31c3eb32ecb73a7dff7650b346e0edbb471b2b6db308e7a5430673a3d: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: + Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured + outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example + names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. + Current execution instructions are explicitly linked to event-lab/index.md. Current prose replacement + begins: ''- Agent thread: ''.' + a147e49be39803c15924b5b1afa506dfcfd9db543e37dfc5f984aebf685aa96a: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: + Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured + outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example + names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. + Current execution instructions are explicitly linked to event-lab/index.md. Current prose replacement + begins: ''- Agent thread: ''.' + 8ad3fbef8612b932686e896eb20f5dbf33be2927c10d9f67dd72440f9359b068: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: + Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured + outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example + names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. + Current execution instructions are explicitly linked to event-lab/index.md. Current prose replacement + begins: ''후속 보정: vnet workload의 OTEL_SERVICE_NAME을 sre-event-lab-example로 변경하고 alert/evidence query도 + 이 deployment-unique 값으로 제한했다. 변경 후 새 role name으로 ''.' + tables: + 909ff96fff1da5b1cecb3b0a52c0e9d9d9a28c7ac806be0b82f2a20a78b52dce: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: + Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured + outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example + names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. + Current execution instructions are explicitly linked to event-lab/index.md. Current tables replacement + begins: ''Log Analytics | law-sre-event-lab-example | ✅''.' + c019548adf1e89b3b9d0738c57fbb330eb8bc67f3e8616347a1ae7a28c3b0627: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: + Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured + outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example + names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. + Current execution instructions are explicitly linked to event-lab/index.md. Current tables replacement + begins: ''Application Insights | appi-sre-event-lab-example | ✅''.' + 009b1f97083a84aea95449b9595f90dc667b4d8de5276bcba9a0bba4daf6c550: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: + Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured + outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example + names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. + Current execution instructions are explicitly linked to event-lab/index.md. Current tables replacement + begins: ''Storage | stexample00000000, Blob private endpoint | ✅''.' + d7fd4a1cb28109a4524750adffd0bebe2ece8457de6d9df13dd49556d4134d11: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: + Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured + outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example + names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. + Current execution instructions are explicitly linked to event-lab/index.md. Current tables replacement + begins: ''Agent | sre-devguidesample-example | ✅ Running''.' + images: + 98692385d5dc253eebe2615d8858b1f470fc703c18faa1171834eca38339fe49: Preserved byte-for-byte in docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/investigation.gif + (SHA-256 3b130e64f7566503dd1a2706ccbf46bd9d9eeef2bc555e2cb57dcc6a42f72025); sample.yml publishes + it as validation-results/images/s1-investigation.gif to distinguish the three same-basename scenario + captures. Alt text is unchanged. + 94f1c44330f916dca1ce2dde914e40fcb8959302e54ab2d3bd90937cf15090bf: Preserved byte-for-byte in docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/investigation.gif + (SHA-256 4ff453aa973f9be400790974afe506d7b6628c81cbc6f69eaef5e4a9e28f0510); sample.yml publishes + it as validation-results/images/s2-investigation.gif to distinguish the three same-basename scenario + captures. Alt text is unchanged. + eb3e116af988c0bcb102a06972938519582b7f650db0897994ff117767ae0957: Preserved byte-for-byte in docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/investigation.gif + (SHA-256 5102eda3c2346968ea0deeae2e7cec434a864994f7ae869b377c12fc948a9eb5); sample.yml publishes + it as validation-results/images/s3-investigation.gif to distinguish the three same-basename scenario + captures. Alt text is unchanged. + local_link_labels: + fabd7a655161bce48b915396b69df3bf2d77130e4bb0f5f9bbe4c626f890932a: Replaced retired path label with + '현재 실습 문서' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, pointing + to docs/services/azure-monitor/azure-sre-agent/event-lab/index.md. The current canonical source + or owning-sample target was checked; this is a source-link/reader-label migration, not removal of + the referenced content. + 176e60b305e09e5517119889b188d142890ad06562a56bee19e69327bc4f1709: Replaced retired path label with + '현재 실습 문서' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, pointing + to docs/services/azure-monitor/azure-sre-agent/event-lab/index.md. The current canonical source + or owning-sample target was checked; this is a source-link/reader-label migration, not removal of + the referenced content. + 4e4283a27c18d5d6aeb2633f6000f9c32ebd13743bcadb62eec08c3451132d55: Retained label '결론 frame' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/07-conclusion.png; + docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/07-conclusion.png; + docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/07-conclusion.png. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + 22267b2e1c53a41a357f022e22cd0b554d3413718ed88f6db4c317745ed9ac17: Retained label '실제 event timeline' + in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/timeline.md; + docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/timeline.md; docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.md. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + 3efcbd689fedb88f8a060668e0f285de17f2e95ad652571306c7deda71ede7b1: Retained label 'Mermaid sequence + source' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, pointing to + docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/timeline.mmd; docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/timeline.mmd; + docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.mmd. The + current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. - baseline_path: mysql/nodejs/db_upgrade_zero_downtime.md pages_path: guides/azure-database-for-mysql/blue-green-upgrade/index.md reviewed_changes: {} @@ -185,10 +1486,49 @@ documents: reviewed_changes: {} - baseline_path: ptu_lb/ptu-lb-test.md pages_path: guides/azure-openai/adaptive-ptu-load-balancing/index.md - reviewed_changes: {} + reviewed_changes: + prose: + fe5088d17216c7a31ebc8e6a922bc8fb7daaf1b8f7f863e6f428a52d1bfc4092: 'In docs/services/azure-openai/adaptive-ptu-load-balancing/index.md: + The redundant 메인 문서: ptu-lb-test.md self-link was removed from 파일 구성; the same guide is this canonical + document. The editable diagram, renderer, Python controller, dependencies and optional PowerShell + implementation remain in docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks. + Current prose replacement begins: ''- 런북 잡이 워커에서 Completed로 종료''.' + local_link_labels: + 782c9f35e32cc1974dd512db107e29fca6fdf72fa3b9a230282acd141f20784c: In docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, + section "파일 구성" drops only the redundant link to this same guide; its title, problem statement and + content are retained at the canonical path. + d8340c1aaa9e95e871d626647970820cd4d84b2ebd67c0908548b3af1d912f75: Retained label 'ptu_architecture.excalidraw' + in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/ptu_architecture.excalidraw. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + 3780ddb85f0f2468a666925639ce03196d37651ae07198d5f07178660b4cb73a: Retained label 'ptu_architecture.png' + in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/ptu_architecture.png. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + a44631d85720594c37c72f21f5d6086dd8e36ef4aa4df5d6ef8084402a49354b: Retained label 'render_excalidraw.py' + in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/render_excalidraw.py. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + 7ee017639b4fc20bb4aa51df646d0915fee435c7d2b1fb7422ac484d9d035c99: Retained label 'runbooks/adaptive_tpm_quota_controller.py' + in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/adaptive_tpm_quota_controller.py. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + 59e441dc4125b74571fdfbbe9d389c5f81a03e3b76542f71a4b770af7cbab02d: Retained label 'runbooks/requirements.txt' + in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/requirements.txt. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + 83d324c4a28e9157567e4fd24234bcd1f42e9974ac6778197bb506e0a84deda3: Retained label 'runbooks/optional_adaptive_tpm_quota_controller.ps1' + in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/optional_adaptive_tpm_quota_controller.ps1. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. - baseline_path: redis/nodejs/cluster_failover_recovery.md pages_path: cases/azure-managed-redis/cluster-failover-recovery/index.md - reviewed_changes: {} + reviewed_changes: + local_link_labels: + 1b08bfef216eff6b14e0ccca69a06c7914498e7563355de0e674d34ee3cc9b7b: Retained label 'cluster_example/' + in docs/services/azure-managed-redis/cluster-failover-recovery/index.md, pointing to docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. dispositions: .azure/deployment-plan.md: status: excluded-local-state diff --git a/tests/docs/test_pre_pages.py b/tests/docs/test_pre_pages.py index 5616196a..5087f331 100644 --- a/tests/docs/test_pre_pages.py +++ b/tests/docs/test_pre_pages.py @@ -26,6 +26,8 @@ BASELINE = "a4e680116db1016a698e64baae9efde858a8eafa" PAGES = "9ace966742ff92a00fc84bcc21529af47b8661af" MANIFEST = ROOT / "scripts/docs/pre_pages_inventory.yml" +FINGERPRINT = "a" * 64 +CONTENT_REASON = "Preserved in docs/services/service/topic/samples/evidence/README.md under Historical commands." def git(repo: Path, *arguments: str) -> bytes: @@ -69,7 +71,7 @@ def manifest_data() -> dict: { "baseline_path": "old/guide.md", "pages_path": "guides/service/topic/index.md", - "reviewed_changes": {"prose": {"fingerprint": "Preserved in sample README."}}, + "reviewed_changes": {"prose": {FINGERPRINT: CONTENT_REASON}}, } ], "dispositions": { @@ -123,7 +125,7 @@ def test_inventory_loads_deeply_immutable_models(tmp_path: Path, manifest_data: with pytest.raises(TypeError): inventory.dispositions[PurePosixPath("other.md")] = disposition with pytest.raises(TypeError): - document.reviewed_changes["prose"]["fingerprint"] = "changed" + document.reviewed_changes["prose"][FINGERPRINT] = "changed" with pytest.raises(TypeError): document.reviewed_changes["headings"] = {} @@ -255,7 +257,7 @@ def raw_inventory() -> str: pages_path: guides/service/topic/index.md reviewed_changes: prose: - fingerprint: Preserved in the sample README. + {FINGERPRINT}: {CONTENT_REASON} dispositions: old/README.md: status: replaced-summary @@ -280,9 +282,9 @@ def raw_inventory() -> str: "prose", ), ( - " fingerprint: Preserved in the sample README.", - " fingerprint: Lost reason.\n fingerprint: Preserved in the sample README.", - "fingerprint", + f" {FINGERPRINT}: {CONTENT_REASON}", + f" {FINGERPRINT}: Lost reason.\n {FINGERPRINT}: {CONTENT_REASON}", + FINGERPRINT, ), ( " old/README.md:\n", diff --git a/tests/docs/test_pre_pages_content.py b/tests/docs/test_pre_pages_content.py new file mode 100644 index 00000000..eed5105d --- /dev/null +++ b/tests/docs/test_pre_pages_content.py @@ -0,0 +1,355 @@ +from __future__ import annotations + +from dataclasses import FrozenInstanceError +from collections import Counter +from hashlib import sha256 +import json +from pathlib import Path, PurePosixPath + +import pytest +import yaml + +from scripts.docs.pre_pages import AuditFormatError, git_text, load_inventory +from scripts.docs.pre_pages_content import ( + DocumentPreservation, + MarkdownStructure, + StructureFinding, + audit_document_content, + compare_markdown, + extract_markdown_structure, + write_content_review_json, +) + + +ROOT = Path(__file__).parents[2] +BASELINE_PATH = PurePosixPath("old/guide.md") +CURRENT_PATH = PurePosixPath("docs/services/service/topic/index.md") +REASON = ( + "Preserved in docs/services/service/topic/samples/evidence/README.md " + "under Historical commands; the canonical guide now links to that evidence." +) + + +def fingerprint(category: str, value: str) -> str: + return sha256(f"{category}\0{value}".encode("utf-8")).hexdigest() + + +def compare(baseline: str, current: str, reviewed=None) -> DocumentPreservation: + return compare_markdown( + BASELINE_PATH, CURRENT_PATH, + extract_markdown_structure(baseline), extract_markdown_structure(current), + reviewed or {}, + ) + + +def test_front_matter_does_not_supply_title_or_content() -> None: + body = "# 실제 제목\n\n한글 본문." + assert extract_markdown_structure("\ufeff---\ntitle: metadata\nextra: ignored\n---\n" + body) == ( + extract_markdown_structure(body) + ) + assert extract_markdown_structure("---\ntitle: metadata\n---\n").title == "" + + +def test_inline_links_keep_labels_not_destinations_or_titles() -> None: + first = extract_markdown_structure( + '# 제목\n\n[공식 문서](https://example.com/a_(b) "title") 및 [예제](../old.md).' + ) + second = extract_markdown_structure( + "# 제목\n\n[공식 문서](./new.md) 및 [예제](https://example.org/sample)." + ) + assert first.prose == second.prose == ("공식 문서 및 예제.",) + assert first.local_link_labels == ("예제",) + assert second.local_link_labels == ("공식 문서",) + + +def test_reference_links_and_images_use_definitions_not_definition_prose() -> None: + structure = extract_markdown_structure( + "# Title\n\n[Guide][g] and [More][] and [Short].\n\n![Diagram][img]\n\n" + "[g]: ../guide.md\n[More]: https://example.com\n[Short]: #details\n" + '[img]: ../images/plot.svg "Caption"\n' + ) + assert structure.prose == ("Guide and More and Short.",) + assert structure.local_link_labels == ("Guide", "Short") + assert structure.images == ("Diagram\nplot.svg",) + + +def test_comments_and_generated_wrappers_do_not_hide_their_markdown_content() -> None: + plain = "# 제목\n\n본문.\n\n## 다음\n\n내용." + wrapped = ( + '\n
\n' + '# 제목\n\n본문.\n\n
\n' + "## 다음\n\n내용.\n
\n
" + ) + assert extract_markdown_structure(plain) == extract_markdown_structure(wrapped) + + +def test_html_comments_ignore_fake_fences_but_not_inline_code_literals() -> None: + structure = extract_markdown_structure( + "Before `` after.\n\n\n\nKept." + ) + assert structure.prose == ("Before after.", "Kept.") + assert structure.code == () + + +def test_whitespace_line_endings_list_and_heading_numbers_normalize() -> None: + first = "# 1. 제목\r\n\r\n단어 둘\r\n이어짐.\r\n\r\n2. 항목\r\n3) 다음\r\n\r\n## 2.3 절" + second = "# 9) 제목\n\n단어 둘 이어짐.\n\n1) 항목\n8. 다음\n\n## 10.4. 절" + assert extract_markdown_structure(first) == extract_markdown_structure(second) + assert extract_markdown_structure(first).prose == ("단어 둘 이어짐.", "1. 항목", "1. 다음") + + +def test_nfkc_preserves_korean_text() -> None: + structure = extract_markdown_structure("# 1. 제목\n\nABC 123 한글") + assert structure.title == "제목" + assert structure.prose == ("ABC 123 한글",) + + +def test_underscores_in_identifiers_are_not_emphasis_or_redaction() -> None: + structure = extract_markdown_structure("LAB_RESOURCE_GROUP and x__y__z with _emphasis_.") + assert structure.prose == ("LAB_RESOURCE_GROUP and x__y__z with emphasis.",) + + +def test_inline_code_is_literal_not_a_link_or_html_wrapper() -> None: + structure = extract_markdown_structure("Run `[label](../real.md)` and `**literal** `.") + assert structure.prose == ("Run [label](../real.md) and **literal** .",) + assert not structure.local_link_labels + + +def test_heading_years_are_not_removed_as_list_numbering() -> None: + assert extract_markdown_structure("## 2026 findings").headings == ("2026 findings",) + + +def test_setext_headings_have_the_same_text_as_atx_headings() -> None: + assert extract_markdown_structure("제목\n===\n\n절\n---\n\n본문") == ( + extract_markdown_structure("# 제목\n\n## 절\n\n본문") + ) + + +def test_code_language_is_fingerprinted() -> None: + result = compare("```bash\nrun\n```", "```text\nrun\n```") + assert result.missing == (StructureFinding("code", fingerprint("code", "bash\nrun"), "bash\nrun"),) + + +def test_code_indentation_and_internal_spaces_are_semantic() -> None: + before = "```python\nif True:\n print('a b')\n```" + after = "```python\nif True:\n print('a b')\n```" + assert len(compare(before, after).missing) == 1 + assert extract_markdown_structure(before).code == ("python\nif True:\n print('a b')",) + + +def test_code_line_endings_trailing_spaces_and_fence_style_normalize() -> None: + assert compare("```sh\r\nrun \r\nnext\r\n```", "~~~~sh\nrun\nnext\n~~~~").status == "preserved" + + +def test_fences_protect_comments_markdown_and_shorter_fences_inside_code() -> None: + structure = extract_markdown_structure( + "````md\n# literal\n\n[x](../a.md)\n```python\nprint(1)\n```\n````" + ) + assert structure.code == ("md\n# literal\n\n[x](../a.md)\n```python\nprint(1)\n```",) + assert not structure.title and not structure.prose and not structure.local_link_labels + + +def test_unterminated_fence_does_not_discard_historical_evidence() -> None: + assert extract_markdown_structure("```text\nlast observation").code == ("text\nlast observation",) + + +def test_table_separators_are_ignored_but_all_rows_remain() -> None: + before = "| Header | 값 |\n|:---|---:|\n| A | 10 |\n| B | 20 |\n" + after = "Header|값\n-----|-----\n A | 10\n B | 20\n" + assert extract_markdown_structure(before).tables == ("Header | 값", "A | 10", "B | 20") + assert compare(before, after).status == "preserved" + result = compare(before, "| Header | 값 |\n|---|---|\n| A | 10 |") + assert [finding.category for finding in result.missing] == ["tables"] + + +def test_empty_edge_table_cells_cannot_disappear_without_a_finding() -> None: + before = "||B|\n|---|---|\n||value|" + after = "|B|\n|---|\n|value|" + assert compare(before, after).missing + assert extract_markdown_structure(before).tables == (" | B", " | value") + + +def test_table_literal_pipes_are_not_column_delimiters() -> None: + before = "| A | B |\n|---|---|\n| `x | y` | z |" + structure = extract_markdown_structure(before) + assert structure.tables[-1] == r"x \| y | z" + after = "| A | B |\n|---|---|\n| x | y | z |" + assert compare(before, after).missing[0].category == "tables" + + +def test_image_fingerprints_include_alt_and_basename_not_parent_path() -> None: + first = "![한글 그림](../old/plot.svg?raw=true)" + same = '![한글 그림](images/plot.svg "caption")' + assert compare(first, same).status == "preserved" + assert extract_markdown_structure(first).images == ("한글 그림\nplot.svg",) + assert compare(first, "![다른 그림](images/plot.svg)").missing[0].category == "images" + assert compare(first, "![한글 그림](images/other.svg)").missing[0].category == "images" + + +def test_linked_and_html_images_are_extracted_without_markup_prose() -> None: + md = "[![Chart](images/plot.svg)](https://example.com)" + html = 'Chart' + assert extract_markdown_structure(md).images == extract_markdown_structure(html).images + assert not extract_markdown_structure(md).prose + assert not extract_markdown_structure(html).prose + + +def test_unquoted_html_image_attributes_keep_the_alt_and_filename() -> None: + baseline = "diagram" + assert extract_markdown_structure(baseline).images == ("diagram\nbefore.svg",) + assert compare(baseline, "diagram").missing + + +@pytest.mark.parametrize( + ("category", "block", "value"), + [ + ("title", "# Title", "Title"), + ("headings", "## Heading", "Heading"), + ("prose", "Historical observation.", "Historical observation."), + ("code", "```bash\nrun\n```", "bash\nrun"), + ("tables", "| Row | 20 |\n|---|---|", "Row | 20"), + ("images", "![Evidence](images/proof.png)", "Evidence\nproof.png"), + ("local_link_labels", "[Sample](../sample.md)", "Sample"), + ], +) +def test_each_removed_structure_has_its_own_category_and_exact_fingerprint( + category: str, block: str, value: str, +) -> None: + result = compare(block, "") + assert StructureFinding(category, fingerprint(category, value), value[:160]) in result.missing + assert result.status == "missing" + + +def test_duplicate_historical_structures_require_matching_multiplicity() -> None: + result = compare("Repeated.\n\nRepeated.\n\nRepeated.", "Repeated.\n\nRepeated.") + assert len(result.missing) == 1 + assert result.missing[0].fingerprint == fingerprint("prose", "Repeated.") + + +def test_reviewed_exception_applies_only_to_the_exact_fingerprint() -> None: + result = compare("Removed.\n\nOther.", "", {"prose": {fingerprint("prose", "Removed."): REASON}}) + assert [finding.excerpt for finding in result.reviewed] == ["Removed."] + assert [finding.excerpt for finding in result.missing] == ["Other."] + + +def test_reviewed_status_requires_no_unreviewed_missing_structure() -> None: + result = compare("Removed.", "", {"prose": {fingerprint("prose", "Removed."): REASON}}) + assert result.status == "reviewed" + assert not result.missing and len(result.reviewed) == 1 + + +def test_wrong_exception_category_is_stale_not_a_cross_category_approval() -> None: + with pytest.raises(AuditFormatError, match="stale"): + compare("## Heading", "", {"prose": {fingerprint("headings", "Heading"): REASON}}) + + +def test_stale_exception_is_an_inventory_error_even_after_a_block_is_restored() -> None: + with pytest.raises(AuditFormatError, match="stale"): + compare("Kept.", "Kept.", {"prose": {fingerprint("prose", "Kept."): REASON}}) + + +@pytest.mark.parametrize("reason", ["", " ", None, "updated", "not needed", "Preserved in sample README."]) +def test_exceptions_reject_empty_or_generic_reasons(reason: object) -> None: + with pytest.raises(AuditFormatError, match="reason"): + compare("Removed.", "", {"prose": {fingerprint("prose", "Removed."): reason}}) + + +def test_a_path_alone_does_not_make_a_generic_updated_reason_specific() -> None: + with pytest.raises(AuditFormatError, match="reason"): + compare( + "Removed.", "", + {"prose": {fingerprint("prose", "Removed."): "Updated docs/services/service/topic/index.md."}}, + ) + + +def test_specific_korean_preservation_reasons_are_valid() -> None: + reason = ( + "docs/services/service/topic/samples/evidence/README.md의 이관 전 조사 명령 절에 " + "원문을 보존하고 현재 가이드에서 그 역사적 증거로 연결한다." + ) + result = compare("Removed.", "", {"prose": {fingerprint("prose", "Removed."): reason}}) + assert result.status == "reviewed" + + +@pytest.mark.parametrize("key", ["hash", "A" * 64, "g" * 64, "a" * 63, 1]) +def test_inventory_rejects_non_sha256_exception_keys(tmp_path: Path, key: object) -> None: + data = yaml.safe_load((ROOT / "scripts/docs/pre_pages_inventory.yml").read_text()) + data["documents"][0]["reviewed_changes"] = {"prose": {key: REASON}} + path = tmp_path / "inventory.yml" + path.write_text(yaml.safe_dump(data, allow_unicode=True), encoding="utf-8") + with pytest.raises(AuditFormatError, match="fingerprint"): + load_inventory(path) + + +def test_inventory_rejects_unknown_exception_categories(tmp_path: Path) -> None: + data = yaml.safe_load((ROOT / "scripts/docs/pre_pages_inventory.yml").read_text()) + data["documents"][0]["reviewed_changes"] = {"anything": {"a" * 64: REASON}} + path = tmp_path / "inventory.yml" + path.write_text(yaml.safe_dump(data, allow_unicode=True), encoding="utf-8") + with pytest.raises(AuditFormatError, match="category"): + load_inventory(path) + + +def test_similarity_cannot_approve_even_a_tiny_loss() -> None: + common = "A detailed retained observation. " * 400 + result = compare(common + "\n\nLost.", common) + assert result.text_similarity > 0.99 + assert result.status == "missing" + assert [finding.excerpt for finding in result.missing] == ["Lost."] + + +def test_finding_excerpts_are_limited_to_160_normalized_characters() -> None: + text = "단어 " * 200 + finding = compare(text, "").missing[0] + assert len(finding.excerpt) == 160 + assert finding.fingerprint == fingerprint("prose", text.strip()) + + +def test_public_models_are_frozen() -> None: + structure = extract_markdown_structure("Text.") + result = compare("Text.", "") + for model, field in ((structure, "title"), (result, "status"), (result.missing[0], "category")): + with pytest.raises(FrozenInstanceError): + setattr(model, field, "changed") + assert isinstance(structure, MarkdownStructure) + assert result.baseline_path == BASELINE_PATH and result.current_path == CURRENT_PATH + + +def test_json_review_is_deterministic_and_preserves_unicode_and_findings(tmp_path: Path) -> None: + results = (compare("한글.", ""), compare("Kept.", "Kept.")) + output = tmp_path / "review.json" + write_content_review_json(output, results) + original = output.read_bytes() + write_content_review_json(output, results) + assert output.read_bytes() == original + data = json.loads(original) + assert data["summary"]["documents"] == 2 + assert data["summary"]["missing"] == 1 + assert data["documents"][0]["baseline_path"] == str(BASELINE_PATH) + assert data["documents"][0]["missing"][0]["excerpt"] == "한글." + assert "한글".encode() in original + + +def test_real_repository_has_62_documents_and_no_unreviewed_content_losses() -> None: + inventory = load_inventory(ROOT / "scripts/docs/pre_pages_inventory.yml") + results = audit_document_content(ROOT, inventory) + assert len(results) == 62 + assert all((ROOT / result.current_path).is_file() for result in results) + assert not [(str(result.baseline_path), len(result.missing)) for result in results if result.missing] + + +def test_historical_reader_map_preserves_all_six_detailed_audience_rows_in_owning_sample() -> None: + inventory = load_inventory(ROOT / "scripts/docs/pre_pages_inventory.yml") + baseline = git_text(ROOT, inventory.baseline_commit, PurePosixPath("memory/agent-memory/README.md")) + original_map = baseline.split("## 문서 구성\n", 1)[1].split("\n---", 1)[0] + topic = ROOT / "docs/services/microsoft-foundry/agent-memory" + sample = topic / "samples/research-artifacts/README.md" + restored = extract_markdown_structure(sample.read_text(encoding="utf-8")) + expected = extract_markdown_structure(original_map) + assert len(expected.tables) == 7 + assert not (Counter(expected.tables) - Counter(restored.tables)) + assert "문서 구성" in restored.headings + assert "samples/research-artifacts/README.md#historical-reader-map-pre-pages" in ( + (topic / "index.md").read_text(encoding="utf-8") + ) + assert "not a current product-support statement" in sample.read_text(encoding="utf-8") From 99b74f22b51ef089fa7b2485af50cc74af2eeed4 Mon Sep 17 00:00:00 2001 From: hellices Date: Mon, 14 Sep 2026 01:12:27 +0900 Subject: [PATCH 06/26] fix(docs): bind preservation approvals to current evidence Bound each reviewed baseline loss to an exact missing count and verified current structure or file evidence. Migrate all 235 approvals, preserve quoted code semantics, reject generic review reasons, and regress the Nginx and duplicate-link deletion loopholes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/docs/pre_pages.py | 172 +- scripts/docs/pre_pages_content.py | 131 +- scripts/docs/pre_pages_inventory.yml | 5021 +++++++++++++++++++------- tests/docs/test_pre_pages.py | 34 +- tests/docs/test_pre_pages_content.py | 377 +- 5 files changed, 4378 insertions(+), 1357 deletions(-) diff --git a/scripts/docs/pre_pages.py b/scripts/docs/pre_pages.py index 3996337b..2f90f798 100644 --- a/scripts/docs/pre_pages.py +++ b/scripts/docs/pre_pages.py @@ -1,14 +1,15 @@ -"""Fixed pre-Pages inventory and Git lineage (not a content-preservation audit).""" +"""Fixed pre-Pages inventory, reviewed-evidence schema, and Git lineage.""" from __future__ import annotations from collections.abc import Hashable -from dataclasses import dataclass +from dataclasses import asdict, dataclass, field from pathlib import Path, PurePosixPath, PureWindowsPath import re import subprocess from types import MappingProxyType from typing import Mapping +import unicodedata import yaml @@ -40,19 +41,48 @@ def construct_mapping(self, node: yaml.MappingNode, deep: bool = False) -> dict: return super().construct_mapping(node, deep=deep) +@dataclass(frozen=True) +class StructureEvidence: + """A normalized current structure with exact (not minimum) multiplicity.""" + + path: PurePosixPath + category: str + fingerprint: str + count: int + kind: str = field(init=False, default="structure") + + +@dataclass(frozen=True) +class FileEvidence: + """Exact current file bytes, including binary artifacts.""" + + path: PurePosixPath + sha256: str + kind: str = field(init=False, default="file") + + +@dataclass(frozen=True) +class ReviewedChange: + """A bounded baseline loss justified by immutable current evidence references.""" + + missing_count: int + reason: str + evidence: tuple[StructureEvidence | FileEvidence, ...] + + @dataclass(frozen=True) class BaselineDocument: baseline_path: PurePosixPath pages_path: PurePosixPath - reviewed_changes: Mapping[str, Mapping[str, str]] + reviewed_changes: Mapping[str, Mapping[str, ReviewedChange]] def __post_init__(self) -> None: object.__setattr__( self, "reviewed_changes", MappingProxyType({ - category: MappingProxyType(dict(reasons)) - for category, reasons in self.reviewed_changes.items() + category: MappingProxyType(dict(approvals)) + for category, approvals in validate_reviewed_changes(self.reviewed_changes).items() }), ) @@ -128,44 +158,110 @@ def _relative_path(value: object, label: str) -> PurePosixPath: ) +def _positive_count(value: object, label: str) -> int: + if type(value) is not int or value <= 0: + raise AuditFormatError(f"{label} must be a positive integer") + return value + + +def _sha256(value: object, label: str) -> str: + if not isinstance(value, str) or not re.fullmatch(r"[0-9a-f]{64}", value): + raise AuditFormatError(f"{label} must be lowercase SHA-256") + return value + + +def _review_reason(value: object, label: str) -> str: + reason = _nonempty_string(value, label) + text = unicodedata.normalize("NFKC", reason).casefold() + text = re.sub(r"(?:https?://|(?:docs|samples)/)\S+", " ", text) + text = re.sub( + r"\b(?:safety|reason|review(?:ed)?|approval|approved?|preservation|replacement|migration|updated?|revised?|" + r"changed?|removed?|deleted?|retained?|preserved?|replaced?|migrated?|" + r"needed|necessary|unnecessary|longer|anymore|during|before|after|because|content|document|documentation|" + r"block|material|stuff|latest|current|previous|old|new|" + r"a|an|the|and|or|as|at|in|on|to|of|for|from|with|by|" + r"this|that|these|those|it|its|is|was|are|were|be|been|not|no|now)\b", + " ", text, + ) + text = re.sub( + r"\b(?:안전|검토|사유|이유|보존|대체|이관|갱신|업데이트|수정|변경|삭제|제거|" + r"유지|불필요|필요|내용|문서|콘텐츠|최신|현재|해당|않|없|기존|이전|" + r"과정|일반적|쓰이지|부분|항목|정리|적절|대한|거쳐)[가-힣]*" + r"|\b(?:이|그|더|이상|위해|때문에)\b", + " ", text, + ) + concrete = re.findall(r"[a-z가-힣][a-z0-9가-힣_.-]*", text) + if len(reason.strip()) < 40 or len(concrete) < 2 or sum(map(len, concrete)) < 12: + raise AuditFormatError( + f"{label} must describe concrete replacement/preservation details, not generic-only approval phrases" + ) + return reason + + +def _evidence_reference(value: object, label: str) -> StructureEvidence | FileEvidence: + if isinstance(value, (StructureEvidence, FileEvidence)): + value = {**asdict(value), "path": str(value.path)} + if not isinstance(value, dict): + raise AuditFormatError(f"{label} must be a mapping") + kind = value.get("kind") + if kind not in ("structure", "file"): + raise AuditFormatError(f"{label}.kind must be structure or file") + fields = {"kind", "path", "category", "fingerprint", "count"} if kind == "structure" else { + "kind", "path", "sha256", + } + raw = _fields(value, fields, label) + path = _relative_path(raw["path"], f"{label}.path") + if kind == "file": + return FileEvidence(path, _sha256(raw["sha256"], f"{label}.sha256")) + category = raw["category"] + if category not in STRUCTURE_CATEGORIES: + raise AuditFormatError(f"{label}.category is not a known structure category") + return StructureEvidence( + path, category, _sha256(raw["fingerprint"], f"{label}.fingerprint"), + _positive_count(raw["count"], f"{label}.count"), + ) + + def validate_reviewed_changes( value: object, label: str = "reviewed_changes", -) -> Mapping[str, Mapping[str, str]]: - """Validate exact source fingerprints and actionable, path-specific review reasons.""" +) -> Mapping[str, Mapping[str, ReviewedChange]]: + """Parse immutable approvals; current file/structure evidence is checked by the content auditor.""" if not isinstance(value, Mapping): raise AuditFormatError(f"{label} must be a mapping") - for category, reasons in value.items(): + parsed = {} + for category, approvals in value.items(): if category not in STRUCTURE_CATEGORIES: raise AuditFormatError(f"{label}: unknown structure category: {category!r}") - if not isinstance(reasons, Mapping): + if not isinstance(approvals, Mapping): raise AuditFormatError(f"{label}.{category} must be a mapping") - for fingerprint, reason in reasons.items(): - if not isinstance(fingerprint, str) or not re.fullmatch(r"[0-9a-f]{64}", fingerprint): - raise AuditFormatError(f"{label}.{category}: fingerprint must be lowercase SHA-256") - reason_label = f"{label}.{category}.{fingerprint}.reason" - _nonempty_string(reason, reason_label) - if len(reason.strip()) < 40 or not re.search( - r"(?:docs|samples)/[^\s`]+" - r"|\bsection\s+[`\"“][^`\"”]+[`\"”]" - r"|\bsafety:\s+.{25,}", - reason, - re.IGNORECASE, - ): - raise AuditFormatError( - f"{reason_label} must name a specific replacement path, quoted section, " - "or explicit safety: transformation, not a generic approval" - ) - if not re.search( - r"\b(?:preserv\w*|replac\w*|restor\w*|retain\w*|remain\w*|unchanged|" - r"redact\w*|safety|renam\w*|remov\w*|migrat\w*)\b" - r"|보존|대체|복원|유지|비식별|이관|치환|삭제", - reason, - re.IGNORECASE, - ): - raise AuditFormatError( - f"{reason_label} must explain preservation, replacement, or safety, not just 'updated'" - ) - return value + parsed[category] = {} + for fingerprint, approval in approvals.items(): + _sha256(fingerprint, f"{label}.{category}.fingerprint") + entry_label = f"{label}.{category}.{fingerprint}.approval" + if isinstance(approval, ReviewedChange): + approval = { + "missing_count": approval.missing_count, + "reason": approval.reason, + "evidence": approval.evidence, + } + raw = _fields(approval, {"missing_count", "reason", "evidence"}, entry_label) + count = _positive_count(raw["missing_count"], f"{entry_label}.missing_count") + reason = _review_reason(raw["reason"], f"{entry_label}.reason") + if not isinstance(raw["evidence"], (list, tuple)) or not raw["evidence"]: + raise AuditFormatError(f"{entry_label}.evidence must be a non-empty sequence") + references = [] + seen = set() + for index, reference in enumerate(raw["evidence"]): + reference = _evidence_reference(reference, f"{entry_label}.evidence[{index}]") + key = (reference.kind, reference.path) + if isinstance(reference, StructureEvidence): + key += (reference.category, reference.fingerprint) + if key in seen: + raise AuditFormatError(f"{entry_label}: duplicate evidence reference: {reference.path}") + seen.add(key) + references.append(reference) + parsed[category][fingerprint] = ReviewedChange(count, reason, tuple(references)) + return parsed def load_inventory(path: Path) -> PrePagesInventory: @@ -178,8 +274,8 @@ def load_inventory(path: Path) -> PrePagesInventory: {"version", "baseline_commit", "pages_commit", "rename_similarity", "documents", "dispositions"}, str(path), ) - if type(data["version"]) is not int or data["version"] != 1: - raise AuditFormatError("version must be 1") + if type(data["version"]) is not int or data["version"] != 2: + raise AuditFormatError("version must be 2 (evidence-bound content approvals)") baseline = _commit_hash(data["baseline_commit"], "baseline_commit") pages = _commit_hash(data["pages_commit"], "pages_commit") similarity = data["rename_similarity"] diff --git a/scripts/docs/pre_pages_content.py b/scripts/docs/pre_pages_content.py index f14781b1..c45a6ba0 100644 --- a/scripts/docs/pre_pages_content.py +++ b/scripts/docs/pre_pages_content.py @@ -16,7 +16,9 @@ from scripts.docs.pre_pages import ( AuditFormatError, + FileEvidence, PrePagesInventory, + ReviewedChange, STRUCTURE_CATEGORIES, git_text, resolve_current_documents, @@ -72,6 +74,15 @@ def _space(text: str) -> str: return " ".join(text.split()) +def _unquote(line: str, depth: int) -> str | None: + for _ in range(depth): + prefix = re.match(r"^[ \t]{0,3}>[ \t]?", line) + if prefix is None: + return None + line = line[prefix.end():] + return line + + def _segments(text: str) -> list[tuple[str | None, str]]: """Separate fences before applying any prose-only normalization.""" segments = [] @@ -80,15 +91,23 @@ def _segments(text: str) -> list[tuple[str | None, str]]: fence = "" language = "" indent = "" + quote_depth = 0 comment = False for line in text.split("\n"): if fence: - if re.fullmatch(r"\s*" + re.escape(fence[0]) + "{" + str(len(fence)) + r",}\s*", line): + code_line = _unquote(line, quote_depth) + if code_line is None: segments.append((language, "\n".join(code).strip("\n"))) fence, code = "", [] + elif re.fullmatch( + r"\s*" + re.escape(fence[0]) + "{" + str(len(fence)) + r",}\s*", code_line, + ): + segments.append((language, "\n".join(code).strip("\n"))) + fence, code = "", [] + continue else: - code.append(line.removeprefix(indent).rstrip()) - continue + code.append(code_line.removeprefix(indent).rstrip()) + continue visible = [] offset = 0 while offset < len(line): @@ -109,11 +128,12 @@ def _segments(text: str) -> list[tuple[str | None, str]]: visible.append(token[0]) offset += token.end() line = "".join(visible) - opening = re.fullmatch(r"([ \t]*)(`{3,}|~{3,})(.*)", line) + opening = re.fullmatch(r"((?:[ \t]{0,3}>[ \t]?)*)([ \t]*)(`{3,}|~{3,})(.*)", line) if opening: segments.append((None, "\n".join(normal))) normal = [] - indent, fence, language = opening.groups() + quotes, indent, fence, language = opening.groups() + quote_depth = quotes.count(">") language = language.strip() else: normal.append(line) @@ -289,49 +309,105 @@ def flush() -> None: ) +def _values(structure: MarkdownStructure, category: str) -> tuple[str, ...]: + if category == "title": + return (structure.title,) if structure.title else () + return getattr(structure, category) + + +def _verify_current_evidence( + repo_root: Path, + baseline_path: PurePosixPath, + reviewed_changes: Mapping[str, Mapping[str, ReviewedChange]], +) -> None: + root = repo_root.resolve() + contents: dict[PurePosixPath, bytes] = {} + structures: dict[PurePosixPath, MarkdownStructure] = {} + for category, approvals in reviewed_changes.items(): + for fingerprint, approval in approvals.items(): + for index, reference in enumerate(approval.evidence): + label = f"{baseline_path}.{category}.{fingerprint}.evidence[{index}] ({reference.path})" + if reference.path not in contents: + try: + path = (root / reference.path).resolve(strict=True) + if not path.is_relative_to(root): + raise AuditFormatError(f"{label}: evidence path is outside repository") + if not path.is_file(): + raise AuditFormatError(f"{label}: evidence path is not a file") + contents[reference.path] = path.read_bytes() + except OSError as error: + raise AuditFormatError(f"{label}: cannot read current evidence: {error}") from error + data = contents[reference.path] + if isinstance(reference, FileEvidence): + actual_hash = sha256(data).hexdigest() + if actual_hash != reference.sha256: + raise AuditFormatError(f"{label}: evidence SHA-256 changed: {actual_hash}") + continue + if reference.path not in structures: + try: + structures[reference.path] = extract_markdown_structure(data.decode("utf-8")) + except UnicodeError as error: + raise AuditFormatError(f"{label}: structure evidence is not UTF-8: {error}") from error + counts = Counter( + sha256(f"{reference.category}\0{value}".encode("utf-8")).hexdigest() + for value in _values(structures[reference.path], reference.category) + ) + actual_count = counts[reference.fingerprint] + if actual_count != reference.count: + raise AuditFormatError( + f"{label}: evidence {reference.category} fingerprint {reference.fingerprint} " + f"count changed: expected {reference.count}, found {actual_count}" + ) + + def compare_markdown( baseline_path: PurePosixPath, current_path: PurePosixPath, baseline: MarkdownStructure, current: MarkdownStructure, - reviewed_changes: Mapping[str, Mapping[str, str]], + reviewed_changes: Mapping[str, Mapping[str, ReviewedChange]], + *, + repo_root: Path | None = None, ) -> DocumentPreservation: + """Compare source structures; approvals require repo_root for fresh evidence reads.""" reviewed_changes = validate_reviewed_changes(reviewed_changes, f"{baseline_path}.reviewed_changes") - missing: list[StructureFinding] = [] - reviewed: list[StructureFinding] = [] - used = set() + raw_missing: list[StructureFinding] = [] for category in STRUCTURE_CATEGORIES: - before = (baseline.title,) if category == "title" and baseline.title else ( - () if category == "title" else getattr(baseline, category) - ) - after = (current.title,) if category == "title" and current.title else ( - () if category == "title" else getattr(current, category) - ) - remaining = Counter(after) - for value in before: + remaining = Counter(_values(current, category)) + for value in _values(baseline, category): if remaining[value]: remaining[value] -= 1 continue fingerprint = sha256(f"{category}\0{value}".encode("utf-8")).hexdigest() - finding = StructureFinding(category, fingerprint, value[:160]) - if fingerprint in reviewed_changes.get(category, {}): - reviewed.append(finding) - used.add((category, fingerprint)) - else: - missing.append(finding) - for category, reasons in reviewed_changes.items(): - for fingerprint in reasons: - if (category, fingerprint) not in used: + raw_missing.append(StructureFinding(category, fingerprint, value[:160])) + missing_counts = Counter((finding.category, finding.fingerprint) for finding in raw_missing) + approved = set() + for category, approvals in reviewed_changes.items(): + for fingerprint, approval in approvals.items(): + actual = missing_counts[(category, fingerprint)] + if not actual: raise AuditFormatError( f"{baseline_path}: stale reviewed_changes.{category}.{fingerprint}; " "the exact baseline structure is not missing" ) + if actual != approval.missing_count: + raise AuditFormatError( + f"{baseline_path}.{category}.{fingerprint}: missing_count changed: " + f"approved {approval.missing_count}, found {actual}" + ) + approved.add((category, fingerprint)) + if approved: + if repo_root is None: + raise AuditFormatError(f"{baseline_path}: repo_root is required to verify current approval evidence") + _verify_current_evidence(repo_root, baseline_path, reviewed_changes) + missing = tuple(f for f in raw_missing if (f.category, f.fingerprint) not in approved) + reviewed = tuple(f for f in raw_missing if (f.category, f.fingerprint) in approved) before_text = "\n".join((baseline.title, *baseline.headings, *baseline.prose)) after_text = "\n".join((current.title, *current.headings, *current.prose)) similarity = SequenceMatcher(None, before_text, after_text, autojunk=False).ratio() status = "missing" if missing else "reviewed" if reviewed else "preserved" return DocumentPreservation( - baseline_path, current_path, status, similarity, tuple(missing), tuple(reviewed), + baseline_path, current_path, status, similarity, missing, reviewed, ) @@ -346,6 +422,7 @@ def audit_document_content( extract_markdown_structure(git_text(repo_root, inventory.baseline_commit, entry.baseline_path)), extract_markdown_structure(documents[entry.baseline_path].body), entry.reviewed_changes, + repo_root=repo_root, ) for entry in inventory.documents ) diff --git a/scripts/docs/pre_pages_inventory.yml b/scripts/docs/pre_pages_inventory.yml index 2ae105ea..9203d816 100644 --- a/scripts/docs/pre_pages_inventory.yml +++ b/scripts/docs/pre_pages_inventory.yml @@ -1,5 +1,5 @@ -# Content exceptions are exact category/SHA-256 reviews, never similarity thresholds. -version: 1 +# Reviewed evidence bindings: exact baseline loss counts and current structure/file proofs. +version: 2 baseline_commit: a4e680116db1016a698e64baae9efde858a8eafa pages_commit: 9ace966742ff92a00fc84bcc21529af47b8661af rename_similarity: 20 @@ -11,26 +11,58 @@ documents: pages_path: guides/microsoft-foundry/codex-closed-network/index.md reviewed_changes: prose: - 118b96a5a8992998657f86c734faad1c91c60b434bf3dc872cd57ed891601410: 'In docs/services/microsoft-foundry/codex-closed-network/index.md: - Safety: the hosts example, Codex TOML base_url and nginx server_name/Host now consistently use example-openai.openai.azure.com - instead of the private endpoint hostname. Loopback addresses, ports, model and proxy configuration - are retained. Current prose replacement begins: ''예: Private Endpoint 도메인이 example-openai.openai.azure.com인 - 경우''.' + 118b96a5a8992998657f86c734faad1c91c60b434bf3dc872cd57ed891601410: + missing_count: 1 + reason: 'In docs/services/microsoft-foundry/codex-closed-network/index.md: Safety: the hosts + example, Codex TOML base_url and nginx server_name/Host now consistently use example-openai.openai.azure.com + instead of the private endpoint hostname. Loopback addresses, ports, model and proxy configuration + are retained. Current prose replacement begins: ''예: Private Endpoint 도메인이 example-openai.openai.azure.com인 + 경우''.' + evidence: + - kind: structure + path: docs/services/microsoft-foundry/codex-closed-network/index.md + category: prose + fingerprint: 0c962db546318c617da81cb569680dacf2a63978a150c40d28211edb5ff7f769 + count: 1 code: - d33ebf7645206700ee597fa0faf3667d1042274f1de382a2b3fca47be72eda91: 'In docs/services/microsoft-foundry/codex-closed-network/index.md: - Safety: the hosts example, Codex TOML base_url and nginx server_name/Host now consistently use example-openai.openai.azure.com - instead of the private endpoint hostname. Loopback addresses, ports, model and proxy configuration - are retained. Current code replacement begins: ''127.0.0.1 example-openai.openai.azure.com''.' - ce8dbbc8cd77131c31f326888caeffd8b6df484eb4b7190d44a05159984b00ca: 'In docs/services/microsoft-foundry/codex-closed-network/index.md: - Safety: the hosts example, Codex TOML base_url and nginx server_name/Host now consistently use example-openai.openai.azure.com - instead of the private endpoint hostname. Loopback addresses, ports, model and proxy configuration - are retained. Current code replacement begins: ''model = "gpt-5.3-codex" model_provider = "custom-provider" - personality = "pragmatic" model_reasoning_effort = "medium" preferred_auth_method''.' - 2e05e66284addd0c672dbe1245bb8761cba1fcc25fbf0797c7b5f7324f7ffd1a: 'In docs/services/microsoft-foundry/codex-closed-network/index.md: - Safety: the hosts example, Codex TOML base_url and nginx server_name/Host now consistently use example-openai.openai.azure.com - instead of the private endpoint hostname. Loopback addresses, ports, model and proxy configuration - are retained. Current code replacement begins: ''worker_processes 1; events { worker_connections 1024; - } http { include mime.types; default_type application/octet-stream; sendfile ''.' + d33ebf7645206700ee597fa0faf3667d1042274f1de382a2b3fca47be72eda91: + missing_count: 1 + reason: 'In docs/services/microsoft-foundry/codex-closed-network/index.md: Safety: the hosts + example, Codex TOML base_url and nginx server_name/Host now consistently use example-openai.openai.azure.com + instead of the private endpoint hostname. Loopback addresses, ports, model and proxy configuration + are retained. Current code replacement begins: ''127.0.0.1 example-openai.openai.azure.com''.' + evidence: + - kind: structure + path: docs/services/microsoft-foundry/codex-closed-network/index.md + category: code + fingerprint: 8800cd6f8474d4cde2fbf104287480472fb9351dd61ba6ffe56ed3dbca6077cc + count: 1 + ce8dbbc8cd77131c31f326888caeffd8b6df484eb4b7190d44a05159984b00ca: + missing_count: 1 + reason: 'In docs/services/microsoft-foundry/codex-closed-network/index.md: Safety: the hosts + example, Codex TOML base_url and nginx server_name/Host now consistently use example-openai.openai.azure.com + instead of the private endpoint hostname. Loopback addresses, ports, model and proxy configuration + are retained. Current code replacement begins: ''model = "gpt-5.3-codex" model_provider = + "custom-provider" personality = "pragmatic" model_reasoning_effort = "medium" preferred_auth_method''.' + evidence: + - kind: structure + path: docs/services/microsoft-foundry/codex-closed-network/index.md + category: code + fingerprint: 17522a8569ba814cf6f0332803b5f4459f6f07624d92d130eeff41169f1c6357 + count: 1 + 2e05e66284addd0c672dbe1245bb8761cba1fcc25fbf0797c7b5f7324f7ffd1a: + missing_count: 1 + reason: 'In docs/services/microsoft-foundry/codex-closed-network/index.md: Safety: the hosts + example, Codex TOML base_url and nginx server_name/Host now consistently use example-openai.openai.azure.com + instead of the private endpoint hostname. Loopback addresses, ports, model and proxy configuration + are retained. Current code replacement begins: ''worker_processes 1; events { worker_connections 1024; + } http { include mime.types; default_type application/octet-stream; sendfile ''.' + evidence: + - kind: structure + path: docs/services/microsoft-foundry/codex-closed-network/index.md + category: code + fingerprint: d3cd1322cefb1098c42bc0d471dbeb009b350a5005323a04100daa6663d292ab + count: 1 - baseline_path: aifoundry/foundry_local.md pages_path: guides/microsoft-foundry/foundry-local-air-gapped/index.md reviewed_changes: {} @@ -38,242 +70,559 @@ documents: pages_path: guides/azure-ai-search/korean-analyzer-comparison/index.md reviewed_changes: prose: - b22a799dcc9854d4f5d04429a05a7d6422303d141148dab593bb00acce598c0e: 'In docs/services/azure-ai-search/korean-analyzer-comparison/index.md: - Safety: the validation/Portal/resource table and cleanup command now use rg-search-example-koreacentral - and search-example-koreacentral-02. The reproduction fence tells readers to prepare their own resources - instead of claiming they already exist. Free tier, region, dataset, analyzer outcomes and execution - commands remain. Current prose replacement begins: ''Azure Portal → search-example-koreacentral-02 - → Indexes → analyzer-compare-idx → Search explorer → 우측 상단 View → JSON 으로 전환하면 JSON query edit''.' + b22a799dcc9854d4f5d04429a05a7d6422303d141148dab593bb00acce598c0e: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/korean-analyzer-comparison/index.md: Safety: the validation/Portal/resource + table and cleanup command now use rg-search-example-koreacentral and search-example-koreacentral-02. + The reproduction fence tells readers to prepare their own resources instead of claiming they + already exist. Free tier, region, dataset, analyzer outcomes and execution commands remain. + Current prose replacement begins: ''Azure Portal → search-example-koreacentral-02 → Indexes + → analyzer-compare-idx → Search explorer → 우측 상단 View → JSON 으로 전환하면 JSON query edit''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/korean-analyzer-comparison/index.md + category: prose + fingerprint: 07fb12f06c34db6a8b43d7af47db3b27b1612fa87fcf3c22fe8711c6233fe6b2 + count: 1 code: - ab88d2080db1de056ee51cb84b7193aac8596fa11ef2b9c91de5c86207b85fa1: 'In docs/services/azure-ai-search/korean-analyzer-comparison/index.md: - Safety: the validation/Portal/resource table and cleanup command now use rg-search-example-koreacentral - and search-example-koreacentral-02. The reproduction fence tells readers to prepare their own resources - instead of claiming they already exist. Free tier, region, dataset, analyzer outcomes and execution - commands remain. Current code replacement begins: ''# 아래 리소스 이름은 예시입니다. 사용할 리소스 그룹과 Azure AI Search - 서비스를 # 먼저 준비한 뒤 실제 이름으로 바꾸십시오. # .env.example 을 .env 로 복사하고 SEARCH_ADMIN_KEY 채우기 # az sear''.' - 2021dfc593d5f03f0377b6e0fa5a3fc2b630131c42d4c9554ed26ee72b15918c: 'In docs/services/azure-ai-search/korean-analyzer-comparison/index.md: - Safety: the validation/Portal/resource table and cleanup command now use rg-search-example-koreacentral - and search-example-koreacentral-02. The reproduction fence tells readers to prepare their own resources - instead of claiming they already exist. Free tier, region, dataset, analyzer outcomes and execution - commands remain. Current code replacement begins: ''az group delete -n rg-search-example-koreacentral - --yes --no-wait''.' + ab88d2080db1de056ee51cb84b7193aac8596fa11ef2b9c91de5c86207b85fa1: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/korean-analyzer-comparison/index.md: Safety: the validation/Portal/resource + table and cleanup command now use rg-search-example-koreacentral and search-example-koreacentral-02. + The reproduction fence tells readers to prepare their own resources instead of claiming they + already exist. Free tier, region, dataset, analyzer outcomes and execution commands remain. + Current code replacement begins: ''# 아래 리소스 이름은 예시입니다. 사용할 리소스 그룹과 Azure AI Search 서비스를 # + 먼저 준비한 뒤 실제 이름으로 바꾸십시오. # .env.example 을 .env 로 복사하고 SEARCH_ADMIN_KEY 채우기 # az sear''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/korean-analyzer-comparison/index.md + category: code + fingerprint: 7fe49a11c6fb5552698cfd4341b2b210918e8aa4b24263739d2a84b678a1e869 + count: 1 + 2021dfc593d5f03f0377b6e0fa5a3fc2b630131c42d4c9554ed26ee72b15918c: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/korean-analyzer-comparison/index.md: Safety: the validation/Portal/resource + table and cleanup command now use rg-search-example-koreacentral and search-example-koreacentral-02. + The reproduction fence tells readers to prepare their own resources instead of claiming they + already exist. Free tier, region, dataset, analyzer outcomes and execution commands remain. + Current code replacement begins: ''az group delete -n rg-search-example-koreacentral --yes + --no-wait''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/korean-analyzer-comparison/index.md + category: code + fingerprint: d858f3fb9fba92fb2b02dcfe8094df7ff46f2287d2cf98cbe16bb7415a772ac7 + count: 1 tables: - 8f5f31717321c430bf0ba0b503369080e3d56e264ff8e5485f681a131bd1fa91: 'In docs/services/azure-ai-search/korean-analyzer-comparison/index.md: - Safety: the validation/Portal/resource table and cleanup command now use rg-search-example-koreacentral - and search-example-koreacentral-02. The reproduction fence tells readers to prepare their own resources - instead of claiming they already exist. Free tier, region, dataset, analyzer outcomes and execution - commands remain. Current tables replacement begins: ''검증 리소스 | RG rg-search-example-koreacentral - / AI Search search-example-koreacentral-02 (Free, koreacentral)''.' - 6558295403a5c68b0f411a9f1628b300c862e6de77493f58d7d166ca093b0aef: 'In docs/services/azure-ai-search/korean-analyzer-comparison/index.md: - Safety: the validation/Portal/resource table and cleanup command now use rg-search-example-koreacentral - and search-example-koreacentral-02. The reproduction fence tells readers to prepare their own resources - instead of claiming they already exist. Free tier, region, dataset, analyzer outcomes and execution - commands remain. Current tables replacement begins: ''Resource Group | rg-search-example-koreacentral - | koreacentral''.' - f8f04e0dfe11f80a9e8f12212b79c433fae1cae02e2c9ff6e3260c76fb09621a: 'In docs/services/azure-ai-search/korean-analyzer-comparison/index.md: - Safety: the validation/Portal/resource table and cleanup command now use rg-search-example-koreacentral - and search-example-koreacentral-02. The reproduction fence tells readers to prepare their own resources - instead of claiming they already exist. Free tier, region, dataset, analyzer outcomes and execution - commands remain. Current tables replacement begins: ''AI Search | search-example-koreacentral-02 - | Free tier (비용 0)''.' + 8f5f31717321c430bf0ba0b503369080e3d56e264ff8e5485f681a131bd1fa91: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/korean-analyzer-comparison/index.md: Safety: the validation/Portal/resource + table and cleanup command now use rg-search-example-koreacentral and search-example-koreacentral-02. + The reproduction fence tells readers to prepare their own resources instead of claiming they + already exist. Free tier, region, dataset, analyzer outcomes and execution commands remain. + Current tables replacement begins: ''검증 리소스 | RG rg-search-example-koreacentral / AI Search + search-example-koreacentral-02 (Free, koreacentral)''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/korean-analyzer-comparison/index.md + category: tables + fingerprint: 0825641417d21dce7f0f353a928da2a4421150d83d38f52886262ac255ab3d13 + count: 1 + 6558295403a5c68b0f411a9f1628b300c862e6de77493f58d7d166ca093b0aef: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/korean-analyzer-comparison/index.md: Safety: the validation/Portal/resource + table and cleanup command now use rg-search-example-koreacentral and search-example-koreacentral-02. + The reproduction fence tells readers to prepare their own resources instead of claiming they + already exist. Free tier, region, dataset, analyzer outcomes and execution commands remain. + Current tables replacement begins: ''Resource Group | rg-search-example-koreacentral | koreacentral''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/korean-analyzer-comparison/index.md + category: tables + fingerprint: 3a5ab857a33cc8eecd997dad2766621c59ea229ebf3d442fcb2d890bae2f4071 + count: 1 + f8f04e0dfe11f80a9e8f12212b79c433fae1cae02e2c9ff6e3260c76fb09621a: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/korean-analyzer-comparison/index.md: Safety: the validation/Portal/resource + table and cleanup command now use rg-search-example-koreacentral and search-example-koreacentral-02. + The reproduction fence tells readers to prepare their own resources instead of claiming they + already exist. Free tier, region, dataset, analyzer outcomes and execution commands remain. + Current tables replacement begins: ''AI Search | search-example-koreacentral-02 | Free tier + (비용 0)''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/korean-analyzer-comparison/index.md + category: tables + fingerprint: 64fe2b3d2f10bf43e295a6dc6956608c2dd63a9573ec516391103397cf07cca0 + count: 1 - baseline_path: aisearch/custom_vectorization/01_custom_embedding_guide.md pages_path: guides/azure-ai-search/custom-embedding-ingestion/index.md reviewed_changes: local_link_labels: - b1fdc5e64b1619b40e77c551df0df2e5a43c7aea3b6b54d76990c9037d6a70ba: Retained label 'tei-adapter' in - docs/services/azure-ai-search/custom-vectorization/index.md, pointing to docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. + b1fdc5e64b1619b40e77c551df0df2e5a43c7aea3b6b54d76990c9037d6a70ba: + missing_count: 1 + reason: Retained label 'tei-adapter' in docs/services/azure-ai-search/custom-vectorization/index.md, + pointing to docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/index.md + category: prose + fingerprint: 2fe6e3de7e64b5c6c52dbe7075d92314a69ff7768dd08a30484f467518d4648b + count: 1 + - kind: file + path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter/Dockerfile + sha256: ef7b10e5dc4c9e63901448a1a86cb0e8697101d201400e69ff3efeb88532277e + - kind: file + path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter/app.py + sha256: c731ce1adc3f64178c018fb46a72262bd4cbbe0435e3e54d220013b2900b5b10 + - kind: file + path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter/k8s/deployment.yaml + sha256: 843672887878b904c9be73126416a97f5f917d6dcd0dcaf0ea8bff3cbacc6496 + - kind: file + path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter/requirements.txt + sha256: 468be2c9de7dbe4976f66a16981d181fe746622ec5f7c6d1e08fd3c9d5a082df - baseline_path: aisearch/custom_vectorization/02_custom_vectorization.md pages_path: guides/azure-ai-search/custom-web-api-vectorization/index.md reviewed_changes: code: - f6df427f69dcd0be8f4e64827e5122f0f63325e86a686ba6a10a3ebd7fb9bf3b: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: - Safety: the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, - acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. - The historical public ingress address became INGRESS_IP; health requests interpolate it and the - index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles and JSON - skill/vectorizer payloads are retained. Current code replacement begins: ''az acr build --registry - acrexamplekrc01 \\ --image bge-m3-embedding:latest \\ --file embedding-api/Dockerfile \\ embedding-api/''.' - b9c12ed1dd63a966a1bd992a0658e489a3a18b7e81bd366b438574d0cf69411c: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: - Safety: the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, - acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. - The historical public ingress address became INGRESS_IP; health requests interpolate it and the - index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles and JSON - skill/vectorizer payloads are retained. Current code replacement begins: ''# Container Apps 환경 생성 - az containerapp env create --name cae-example-koreacentral-01 \\ --resource-group rg-example-koreacentral-01 - --locatio''.' - 49febf4034a25667d4f93acc9c709bf7a9f524ffe7245f598401a7fc23c07f0d: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: - Safety: the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, - acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. - The historical public ingress address became INGRESS_IP; health requests interpolate it and the - index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles and JSON - skill/vectorizer payloads are retained. Current code replacement begins: ''# FQDN 확인 az containerapp - show --name ca-embedding-example-01 \\ --resource-group rg-example-koreacentral-01 \\ --query properties.configurati''.' - 2b9f9401aa63df59fb41f841a26fffb0197d39d39209f50690452830245a6365: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: - Safety: the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, - acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. - The historical public ingress address became INGRESS_IP; health requests interpolate it and the - index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles and JSON - skill/vectorizer payloads are retained. Current code replacement begins: "# Ingress LoadBalancer - IP 확인 INGRESS_IP=$(kubectl get svc -n ingress-nginx ingress-nginx-controller \\ -o jsonpath=''{.status.loadBalancer.ing".' - fdf02e23afdeb9e94a114dc1ef232cfee33079c3229b26931d6fd815fdc87675: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: - Safety: the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, - acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. - The historical public ingress address became INGRESS_IP; health requests interpolate it and the - index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles and JSON - skill/vectorizer payloads are retained. Current code replacement begins: "# Option A (Container - Apps) curl -s https://ca-embedding-example-01.example.koreacentral.azurecontainerapps.io/health - # Option B (AKS + Let''".' - 10245a8f6783fdcd19570715006a62fd315a59b6c0c318354764a251fa63453b: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: - Safety: the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, - acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. - The historical public ingress address became INGRESS_IP; health requests interpolate it and the - index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles and JSON - skill/vectorizer payloads are retained. Current code replacement begins: ''# AI Search MI에 Storage - Blob Data Reader 역할 부여 az role assignment create \\ --role "Storage Blob Data Reader" \\ --assignee-object-id - $(az sea''.' - 64ce0dbb52cab5271fe47078ca1669de9db85c8a1db17efdc350aca4367c0bfc: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: - Safety: the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, - acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. - The historical public ingress address became INGRESS_IP; health requests interpolate it and the - index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles and JSON - skill/vectorizer payloads are retained. Current code replacement begins: ''# 데이터 소스 생성 (Managed - Identity 인증) curl -X POST "$SEARCH_URL/datasources?api-version=2024-07-01" \\ -H "Content-Type: - application/json" -H "ap''.' - 79d5f6459caea12c2e123d026c847442bf0a4d68bdf0a34f16c97a5c5552e1cc: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: - Safety: the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, - acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. - The historical public ingress address became INGRESS_IP; health requests interpolate it and the - index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles and JSON - skill/vectorizer payloads are retained. Current code replacement begins: ''# Option A: Container - Apps FQDN # EMBED_URL="https://ca-embedding-example-01.example.koreacentral.azurecontainerapps.io" - # Option B: AKS Ing''.' - 5de6394c79cd254fed3eacc0dfbd4a3d04417b639fd7368654717aaab969b5e9: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: - Safety: the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, - acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. - The historical public ingress address became INGRESS_IP; health requests interpolate it and the - index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles and JSON - skill/vectorizer payloads are retained. Current code replacement begins: ''az acr build --registry - acrexamplekrc01 \\ --image qwen3-embedding:latest \\ --build-arg EMBEDDING_MODEL=Qwen/Qwen3-Embedding-0.6B - \\ --file em''.' - 07b00a8de2b7a108f5379f986b72b01c7b5343a4567b817116b8ee2deef968fd: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: - Safety: the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, - acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. - The historical public ingress address became INGRESS_IP; health requests interpolate it and the - index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles and JSON - skill/vectorizer payloads are retained. Current code replacement begins: ''az acr build --registry - acrexamplekrc01 \\ --image tei-adapter:latest \\ --file tei-adapter/Dockerfile \\ tei-adapter/''.' + f6df427f69dcd0be8f4e64827e5122f0f63325e86a686ba6a10a3ebd7fb9bf3b: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: Safety: + the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, + acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. + The historical public ingress address became INGRESS_IP; health requests interpolate it and + the index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles + and JSON skill/vectorizer payloads are retained. Current code replacement begins: ''az acr + build --registry acrexamplekrc01 \\ --image bge-m3-embedding:latest \\ --file embedding-api/Dockerfile + \\ embedding-api/''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md + category: code + fingerprint: 01ac7a7f18939e2a42f69003fd8ba131f8763480975620e6aa12d526cb60c896 + count: 1 + b9c12ed1dd63a966a1bd992a0658e489a3a18b7e81bd366b438574d0cf69411c: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: Safety: + the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, + acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. + The historical public ingress address became INGRESS_IP; health requests interpolate it and + the index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles + and JSON skill/vectorizer payloads are retained. Current code replacement begins: ''# Container + Apps 환경 생성 az containerapp env create --name cae-example-koreacentral-01 \\ --resource-group + rg-example-koreacentral-01 --locatio''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md + category: code + fingerprint: 9761607883e08ba00fea28983188fba8443f64ea0e43127e207c845b7e469ac1 + count: 1 + 49febf4034a25667d4f93acc9c709bf7a9f524ffe7245f598401a7fc23c07f0d: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: Safety: + the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, + acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. + The historical public ingress address became INGRESS_IP; health requests interpolate it and + the index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles + and JSON skill/vectorizer payloads are retained. Current code replacement begins: ''# FQDN + 확인 az containerapp show --name ca-embedding-example-01 \\ --resource-group rg-example-koreacentral-01 + \\ --query properties.configurati''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md + category: code + fingerprint: 279d1f23345bdfa20811aadc5db9811d8482fe1e7c86edc50ea060c4002a3fd8 + count: 1 + 2b9f9401aa63df59fb41f841a26fffb0197d39d39209f50690452830245a6365: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: Safety: + the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, + acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. + The historical public ingress address became INGRESS_IP; health requests interpolate it and + the index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles + and JSON skill/vectorizer payloads are retained. Current code replacement begins: "# Ingress + LoadBalancer IP 확인 INGRESS_IP=$(kubectl get svc -n ingress-nginx ingress-nginx-controller + \\ -o jsonpath=''{.status.loadBalancer.ing".' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md + category: code + fingerprint: 53e625b7a88017acad1f2e07cc56dd3b33506fbb73d55d49f694f37e773bd2ce + count: 1 + fdf02e23afdeb9e94a114dc1ef232cfee33079c3229b26931d6fd815fdc87675: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: Safety: + the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, + acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. + The historical public ingress address became INGRESS_IP; health requests interpolate it and + the index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles + and JSON skill/vectorizer payloads are retained. Current code replacement begins: "# Option + A (Container Apps) curl -s https://ca-embedding-example-01.example.koreacentral.azurecontainerapps.io/health + # Option B (AKS + Let''".' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md + category: code + fingerprint: 17cfa9ba6eaf9e2f63e3adf7423a93ae0e5af06ec4509ff7004eada1b35cdad6 + count: 1 + 10245a8f6783fdcd19570715006a62fd315a59b6c0c318354764a251fa63453b: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: Safety: + the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, + acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. + The historical public ingress address became INGRESS_IP; health requests interpolate it and + the index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles + and JSON skill/vectorizer payloads are retained. Current code replacement begins: ''# AI Search + MI에 Storage Blob Data Reader 역할 부여 az role assignment create \\ --role "Storage Blob Data + Reader" \\ --assignee-object-id $(az sea''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md + category: code + fingerprint: 84b84eeda766bc9781801bb68de52dc486c23beb6eb633f58a7c00d3c8c8266b + count: 1 + 64ce0dbb52cab5271fe47078ca1669de9db85c8a1db17efdc350aca4367c0bfc: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: Safety: + the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, + acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. + The historical public ingress address became INGRESS_IP; health requests interpolate it and + the index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles + and JSON skill/vectorizer payloads are retained. Current code replacement begins: ''# 데이터 + 소스 생성 (Managed Identity 인증) curl -X POST "$SEARCH_URL/datasources?api-version=2024-07-01" + \\ -H "Content-Type: application/json" -H "ap''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md + category: code + fingerprint: 6492dab23a8b3d380755d7f033bff97314ad0b1feb7af4443c23c9b36c75082a + count: 1 + 79d5f6459caea12c2e123d026c847442bf0a4d68bdf0a34f16c97a5c5552e1cc: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: Safety: + the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, + acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. + The historical public ingress address became INGRESS_IP; health requests interpolate it and + the index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles + and JSON skill/vectorizer payloads are retained. Current code replacement begins: ''# Option + A: Container Apps FQDN # EMBED_URL="https://ca-embedding-example-01.example.koreacentral.azurecontainerapps.io" + # Option B: AKS Ing''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md + category: code + fingerprint: 4f2e40a08b7671ea1a97dc5acaf78572e37892ddbe38c4fac69911f78f8f1762 + count: 1 + 5de6394c79cd254fed3eacc0dfbd4a3d04417b639fd7368654717aaab969b5e9: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: Safety: + the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, + acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. + The historical public ingress address became INGRESS_IP; health requests interpolate it and + the index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles + and JSON skill/vectorizer payloads are retained. Current code replacement begins: ''az acr + build --registry acrexamplekrc01 \\ --image qwen3-embedding:latest \\ --build-arg EMBEDDING_MODEL=Qwen/Qwen3-Embedding-0.6B + \\ --file em''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md + category: code + fingerprint: d86aaaae53af241b3c93ddb1bb6ebf575f5b1e8b7c14a2e131f11a9dd5aa04b9 + count: 1 + 07b00a8de2b7a108f5379f986b72b01c7b5343a4567b817116b8ee2deef968fd: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: Safety: + the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, + acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. + The historical public ingress address became INGRESS_IP; health requests interpolate it and + the index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles + and JSON skill/vectorizer payloads are retained. Current code replacement begins: ''az acr + build --registry acrexamplekrc01 \\ --image tei-adapter:latest \\ --file tei-adapter/Dockerfile + \\ tei-adapter/''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md + category: code + fingerprint: f3bf8807a657b989f844e86f8b38a29efed3de89e03b46dbb0ff0a530ecc698d + count: 1 tables: - 57c1d18b6d825b1e26c74f243a4a4d2c457a5c89620ea3a1216c611756e8fa87: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: - Safety: the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, - acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. - The historical public ingress address became INGRESS_IP; health requests interpolate it and the - index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles and JSON - skill/vectorizer payloads are retained. Current tables replacement begins: ''AI Search | search-example-koreacentral-01 - (Standard) | 인덱스, 스킬셋, 인덱서, 벡터라이저''.' - f54f82d69e9c634f2418029141412f57d3ded42762a27e5933602792ef702ed4: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: - Safety: the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, - acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. - The historical public ingress address became INGRESS_IP; health requests interpolate it and the - index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles and JSON - skill/vectorizer payloads are retained. Current tables replacement begins: ''HTTPS 엔드포인트 (A) | Container - Apps ca-embedding-example-01 | TLS 자동 적용, 설정 최소''.' - 548fae7f0359388390c89d11fbc7af46deee7b3c5fa287d911af83474708b2b2: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: - Safety: the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, - acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. - The historical public ingress address became INGRESS_IP; health requests interpolate it and the - index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles and JSON - skill/vectorizer payloads are retained. Current tables replacement begins: ''컨테이너 레지스트리 | acrexamplekrc01 - (Basic) | Docker 이미지 저장소''.' - 8138e4867c0d1e56f4766e528db4c4e679db336c97c993fbb39f68557ec8633c: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: - Safety: the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, - acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. - The historical public ingress address became INGRESS_IP; health requests interpolate it and the - index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles and JSON - skill/vectorizer payloads are retained. Current tables replacement begins: ''스토리지 | stexamplekrc01 - | Blob 컨테이너 sample-docs (샘플 문서)''.' + 57c1d18b6d825b1e26c74f243a4a4d2c457a5c89620ea3a1216c611756e8fa87: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: Safety: + the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, + acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. + The historical public ingress address became INGRESS_IP; health requests interpolate it and + the index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles + and JSON skill/vectorizer payloads are retained. Current tables replacement begins: ''AI Search + | search-example-koreacentral-01 (Standard) | 인덱스, 스킬셋, 인덱서, 벡터라이저''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md + category: tables + fingerprint: 4c33ba995dbef22f0ff26a2c3aad57d94262dbd5f90ab536c51837683ad083b2 + count: 1 + f54f82d69e9c634f2418029141412f57d3ded42762a27e5933602792ef702ed4: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: Safety: + the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, + acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. + The historical public ingress address became INGRESS_IP; health requests interpolate it and + the index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles + and JSON skill/vectorizer payloads are retained. Current tables replacement begins: ''HTTPS + 엔드포인트 (A) | Container Apps ca-embedding-example-01 | TLS 자동 적용, 설정 최소''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md + category: tables + fingerprint: bb7969bc36fd9766e24ce264439c72e7ab673107afdb3b60f2f1e0a89767a705 + count: 1 + 548fae7f0359388390c89d11fbc7af46deee7b3c5fa287d911af83474708b2b2: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: Safety: + the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, + acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. + The historical public ingress address became INGRESS_IP; health requests interpolate it and + the index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles + and JSON skill/vectorizer payloads are retained. Current tables replacement begins: ''컨테이너 + 레지스트리 | acrexamplekrc01 (Basic) | Docker 이미지 저장소''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md + category: tables + fingerprint: b2ee49f0f166651a83de73f773270430401a099e4a5f63567018e7408371f41e + count: 1 + 8138e4867c0d1e56f4766e528db4c4e679db336c97c993fbb39f68557ec8633c: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md: Safety: + the hosting/resource rows and deployment fences consistently use search-example-koreacentral-01, + acrexamplekrc01, stexamplekrc01, rg-example-koreacentral-01 and example Container Apps names/FQDNs. + The historical public ingress address became INGRESS_IP; health requests interpolate it and + the index/vectorizer fence now rejects an unset INGRESS_IP. Models, image tags, ports, roles + and JSON skill/vectorizer payloads are retained. Current tables replacement begins: ''스토리지 + | stexamplekrc01 | Blob 컨테이너 sample-docs (샘플 문서)''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md + category: tables + fingerprint: d026ba39766f47adfc44a8ac3345e6f216675f7dc1d01a997b6b69476cdbfb16 + count: 1 - baseline_path: aisearch/custom_vectorization/03_gpu_vllm_rag_guide.md pages_path: guides/azure-ai-search/gpu-vllm-rag/index.md reviewed_changes: prose: - 3bfc033e5ddee3fa15c3a5aadcf984f4220dd4694ababff7c32c2b7c9fa385b5: 'In docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md: - Safety: the index creation URL, GPU node-pool command, adapter image, actual-cluster note and Appendix - B environment now use example Search/AKS/ACR/resource-group names. The T4 SKU, Spot settings, model - configuration, sample sizes and measured results remain. Current prose replacement begins: ''실제 - 구성: aks-example-koreacentral-01 클러스터, rg-example-koreacentral-01 리소스 그룹.''.' - a409e1b42b8f79b1509ac578b4fe0003d60279bc2b585c86a6f1d870b37348fc: 'In docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md: - Safety: the index creation URL, GPU node-pool command, adapter image, actual-cluster note and Appendix - B environment now use example Search/AKS/ACR/resource-group names. The T4 SKU, Spot settings, model - configuration, sample sizes and measured results remain. Current prose replacement begins: ''테스트 - 환경: AKS (aks-example-koreacentral-01, koreacentral), GPU 노드풀 Standard_NC16as_T4_v3 Spot × 3, AI - Search Standard tier. 500개 Azure Docs .m''.' + 3bfc033e5ddee3fa15c3a5aadcf984f4220dd4694ababff7c32c2b7c9fa385b5: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md: Safety: the + index creation URL, GPU node-pool command, adapter image, actual-cluster note and Appendix + B environment now use example Search/AKS/ACR/resource-group names. The T4 SKU, Spot settings, + model configuration, sample sizes and measured results remain. Current prose replacement begins: + ''실제 구성: aks-example-koreacentral-01 클러스터, rg-example-koreacentral-01 리소스 그룹.''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md + category: prose + fingerprint: fc39f364706524a9c1259c0a9f30e54eb7eff623c7cd057e9d0470960fafbc64 + count: 1 + a409e1b42b8f79b1509ac578b4fe0003d60279bc2b585c86a6f1d870b37348fc: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md: Safety: the + index creation URL, GPU node-pool command, adapter image, actual-cluster note and Appendix + B environment now use example Search/AKS/ACR/resource-group names. The T4 SKU, Spot settings, + model configuration, sample sizes and measured results remain. Current prose replacement begins: + ''테스트 환경: AKS (aks-example-koreacentral-01, koreacentral), GPU 노드풀 Standard_NC16as_T4_v3 Spot + × 3, AI Search Standard tier. 500개 Azure Docs .m''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md + category: prose + fingerprint: 1f70bc9f09959411bc3b2a0fd56ba3b541e02e48aa50ecae7d31379f515042a3 + count: 1 code: - 9afb64263f717d27965ac4e39e77664066aeb4cfadd11cbb08aec6a5de2de39d: 'In docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md: - Safety: the index creation URL, GPU node-pool command, adapter image, actual-cluster note and Appendix - B environment now use example Search/AKS/ACR/resource-group names. The T4 SKU, Spot settings, model - configuration, sample sizes and measured results remain. Current code replacement begins: ''SEARCH_URL="https://search-example-koreacentral-01.search.windows.net" - KEY="" curl -X PUT "$SEARCH_URL/indexes/prod-ch''.' - e5f1cb808ab09fbab50f085dbc7df5bef561320d76004392392b1f3824638ed9: 'In docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md: - Safety: the index creation URL, GPU node-pool command, adapter image, actual-cluster note and Appendix - B environment now use example Search/AKS/ACR/resource-group names. The T4 SKU, Spot settings, model - configuration, sample sizes and measured results remain. Current code replacement begins: ''# GPU - Spot 노드풀 (T4 × 1) az aks nodepool add \\ --resource-group rg-example-koreacentral-01 \\ --cluster-name - aks-example-koreacentral-01 \\ --n''.' - c73108ad4cc1e65904f20635d16ed3afd4aedba19d2a8d593702bd955dd8e54a: 'In docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md: - Safety: the index creation URL, GPU node-pool command, adapter image, actual-cluster note and Appendix - B environment now use example Search/AKS/ACR/resource-group names. The T4 SKU, Spot settings, model - configuration, sample sizes and measured results remain. Current code replacement begins: ''apiVersion: - apps/v1 kind: Deployment metadata: name: vllm-embedding labels: app: vllm-embedding spec: replicas: - 1 selector: matchLabels: app''.' + 9afb64263f717d27965ac4e39e77664066aeb4cfadd11cbb08aec6a5de2de39d: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md: Safety: the + index creation URL, GPU node-pool command, adapter image, actual-cluster note and Appendix + B environment now use example Search/AKS/ACR/resource-group names. The T4 SKU, Spot settings, + model configuration, sample sizes and measured results remain. Current code replacement begins: + ''SEARCH_URL="https://search-example-koreacentral-01.search.windows.net" KEY="" + curl -X PUT "$SEARCH_URL/indexes/prod-ch''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md + category: code + fingerprint: c8e541144c6d02da01168ede4088b27ea7d800d59b0588236a8186fa90dec8c7 + count: 1 + e5f1cb808ab09fbab50f085dbc7df5bef561320d76004392392b1f3824638ed9: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md: Safety: the + index creation URL, GPU node-pool command, adapter image, actual-cluster note and Appendix + B environment now use example Search/AKS/ACR/resource-group names. The T4 SKU, Spot settings, + model configuration, sample sizes and measured results remain. Current code replacement begins: + ''# GPU Spot 노드풀 (T4 × 1) az aks nodepool add \\ --resource-group rg-example-koreacentral-01 + \\ --cluster-name aks-example-koreacentral-01 \\ --n''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md + category: code + fingerprint: 5c0242c702f2fc47ad79bdae195c63facd1e7c48d93488bd440f49da1958eaf8 + count: 1 + c73108ad4cc1e65904f20635d16ed3afd4aedba19d2a8d593702bd955dd8e54a: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md: Safety: the + index creation URL, GPU node-pool command, adapter image, actual-cluster note and Appendix + B environment now use example Search/AKS/ACR/resource-group names. The T4 SKU, Spot settings, + model configuration, sample sizes and measured results remain. Current code replacement begins: + ''apiVersion: apps/v1 kind: Deployment metadata: name: vllm-embedding labels: app: vllm-embedding + spec: replicas: 1 selector: matchLabels: app''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/gpu-vllm/index.md + category: code + fingerprint: 44f7bac003382b7b2a0cfecbd238226211eb4bbdfa1cee9ea66559d5dfa0a5a1 + count: 1 - baseline_path: aisearch/custom_vectorization/ref_a10_vs_t4_embedding_benchmark.md pages_path: research/azure-ai-search/a10-vs-t4-embedding-benchmark/index.md reviewed_changes: local_link_labels: - 22986ffc7d5949e14cd6fe25b45adabbf8048d43de235516d27915319b0e9eda: Retained label 'bench/' in docs/services/azure-ai-search/custom-vectorization/a10-vs-t4/index.md, - pointing to docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench. The - current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - c1168e03ae9ae43f12052ccdcab60029b049f217eea89f5193f9471bda029f51: Retained label 'bench_qps_sweep.py' - in docs/services/azure-ai-search/custom-vectorization/a10-vs-t4/index.md, pointing to docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/bench_qps_sweep.py. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. + 22986ffc7d5949e14cd6fe25b45adabbf8048d43de235516d27915319b0e9eda: + missing_count: 1 + reason: Retained label 'bench/' in docs/services/azure-ai-search/custom-vectorization/a10-vs-t4/index.md, + pointing to docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/a10-vs-t4/index.md + category: prose + fingerprint: 97338df5886d61b782ea49a09180fd4552f7074d7069018d74042baf98da0daf + count: 1 + - kind: file + path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/Dockerfile + sha256: d0112226dc8be7b0150158f65b4068e6306f6553f3a859e00484056f6a6c3fd3 + - kind: file + path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/bench_embed_gpu.py + sha256: cbdf418323deead0540d0f824b067e512292373ad4929f4d59d40aa14c777d66 + - kind: file + path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/bench_qps_sweep.py + sha256: db9e561cb63b95891dc1c55e0db55d86d4f3c536cbaedcaf9fadb0075acd23c3 + - kind: file + path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/bench_runner.py + sha256: 19ba637503e64d3bab5024cf5c4de6035956fa552729bb3ff44e23f4b45ff4df + - kind: file + path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/k8s/configmap.yaml + sha256: e39cc542677a13ee563918244d574aa131d7737c42cc3f9ae3c2db049f449404 + - kind: file + path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/k8s/job-bench-concurrency.yaml + sha256: ddf98b58e1de53ccbec3e5609f0198c21c37bb4b509f0a8aa553203931aab94b + - kind: file + path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/k8s/job-bench-indexer.yaml + sha256: baf4006544be251dcd104400fa4b14b62c725efdc8dfa6466701d6418e8dc357 + - kind: file + path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/k8s/job-bench-push.yaml + sha256: d9dbd9a28808094b9551087d90f1dfffa5864ccf5858655ee7fb2e703833b495 + - kind: file + path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/requirements.txt + sha256: 25498c398913549d3a8ed10b9c105067fdfe0a6f95709e15ad44b90cf752773f + c1168e03ae9ae43f12052ccdcab60029b049f217eea89f5193f9471bda029f51: + missing_count: 1 + reason: Retained label 'bench_qps_sweep.py' in docs/services/azure-ai-search/custom-vectorization/a10-vs-t4/index.md, + pointing to docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/bench_qps_sweep.py. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/a10-vs-t4/index.md + category: prose + fingerprint: d00cfbfb081a8504466dd94a869d91b669a549ef3c5ffb63f30b2a771a7d3851 + count: 1 + - kind: file + path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/bench_qps_sweep.py + sha256: db9e561cb63b95891dc1c55e0db55d86d4f3c536cbaedcaf9fadb0075acd23c3 - baseline_path: aisearch/custom_vectorization/ref_bge_m3_vs_qwen3_comparison.md pages_path: research/azure-ai-search/bge-m3-vs-qwen3-embedding/index.md reviewed_changes: tables: - a680b3767ce1c680782fc16c086d62240a2b555db34aeccf79f3d8d12151170c: 'In docs/services/azure-ai-search/custom-vectorization/bge-m3-vs-qwen3/index.md: - Safety: the test environment rows now use search-example-koreacentral-01 and ca-embedding-example-01/ca-qwen3-example-01. - Standard tier, region, index names, 1024 dimensions, HNSW settings and all quality measurements - are retained. Current tables replacement begins: ''AI Search | search-example-koreacentral-01 (Standard, - koreacentral)''.' - 310a5ade796d7ca3f4e83217376141cb3f2a8db6365525105e9adf0805a44664: 'In docs/services/azure-ai-search/custom-vectorization/bge-m3-vs-qwen3/index.md: - Safety: the test environment rows now use search-example-koreacentral-01 and ca-embedding-example-01/ca-qwen3-example-01. - Standard tier, region, index names, 1024 dimensions, HNSW settings and all quality measurements - are retained. Current tables replacement begins: ''HTTPS 엔드포인트 | ca-embedding-example-01 / ca-qwen3-example-01 - (Container Apps)''.' + a680b3767ce1c680782fc16c086d62240a2b555db34aeccf79f3d8d12151170c: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/bge-m3-vs-qwen3/index.md: Safety: + the test environment rows now use search-example-koreacentral-01 and ca-embedding-example-01/ca-qwen3-example-01. + Standard tier, region, index names, 1024 dimensions, HNSW settings and all quality measurements + are retained. Current tables replacement begins: ''AI Search | search-example-koreacentral-01 + (Standard, koreacentral)''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/bge-m3-vs-qwen3/index.md + category: tables + fingerprint: 648697b85ef24e5749aa373ab2b8958bec0c3abb5410d3ff80d1c39104029dff + count: 1 + 310a5ade796d7ca3f4e83217376141cb3f2a8db6365525105e9adf0805a44664: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/bge-m3-vs-qwen3/index.md: Safety: + the test environment rows now use search-example-koreacentral-01 and ca-embedding-example-01/ca-qwen3-example-01. + Standard tier, region, index names, 1024 dimensions, HNSW settings and all quality measurements + are retained. Current tables replacement begins: ''HTTPS 엔드포인트 | ca-embedding-example-01 / + ca-qwen3-example-01 (Container Apps)''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/bge-m3-vs-qwen3/index.md + category: tables + fingerprint: d2ea71e617dac073cef53fa867cd5d3cd32c87754ee9a63ae7e13e7354897ab4 + count: 1 - baseline_path: aisearch/custom_vectorization/ref_chunking_strategies_research.md pages_path: research/azure-ai-search/rag-chunking-strategies/index.md reviewed_changes: prose: - ece93a9ccdfb8c70b20dac23e21e20abb7606051856d876ff784fdcd0e4d84fa: 'In docs/services/azure-ai-search/custom-vectorization/rag-chunking/index.md: - The dated related-document note retains 2026-05-22 and now names the Custom Web API 벡터화 가이드 at docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md - instead of exposing its retired filename. Current prose replacement begins: ''작성일: 2026-05-22 | - 관련 문서: Custom Web API 벡터화 가이드''.' + ece93a9ccdfb8c70b20dac23e21e20abb7606051856d876ff784fdcd0e4d84fa: + missing_count: 1 + reason: 'In docs/services/azure-ai-search/custom-vectorization/rag-chunking/index.md: The dated + related-document note retains 2026-05-22 and now names the Custom Web API 벡터화 가이드 at docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md + instead of exposing its retired filename. Current prose replacement begins: ''작성일: 2026-05-22 + | 관련 문서: Custom Web API 벡터화 가이드''.' + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/rag-chunking/index.md + category: prose + fingerprint: f740e606b5dd81b7bccf148ae21fce1d9afd516bf6930893091316cafff639e6 + count: 1 local_link_labels: - d5750f10270f9509bbb6a2003ad877365cc26a3151f031832a2f5fea0cfa4aa8: Replaced retired path label with - 'Custom Web API 벡터화 가이드' in docs/services/azure-ai-search/custom-vectorization/rag-chunking/index.md, - pointing to docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md. The current - canonical source or owning-sample target was checked; this is a source-link/reader-label migration, - not removal of the referenced content. + d5750f10270f9509bbb6a2003ad877365cc26a3151f031832a2f5fea0cfa4aa8: + missing_count: 1 + reason: Replaced retired path label with 'Custom Web API 벡터화 가이드' in docs/services/azure-ai-search/custom-vectorization/rag-chunking/index.md, + pointing to docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md. The + current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/rag-chunking/index.md + category: local_link_labels + fingerprint: 3532fe14417fc9b274202e0d46b30b6b219ade749affaf6fd42a0eda878352c1 + count: 1 + - kind: structure + path: docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md + category: title + fingerprint: d2db99b98bfff9cde68bcb7933e337db05a9bcdc15635c51d0bfeb63ad756e1e + count: 1 - baseline_path: aisearch/eventual_consistency_reindex.md pages_path: cases/azure-ai-search/eventual-consistency-reindex/index.md reviewed_changes: local_link_labels: - 7fabf46e6de24e286372bf664aaacd6b34e9cfb03c06588a01bf7d803d4addcf: Retained label 'repro.py' in docs/services/azure-ai-search/eventual-consistency-reindex/index.md, - pointing to docs/services/azure-ai-search/eventual-consistency-reindex/samples/reproduction/repro.py. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. + 7fabf46e6de24e286372bf664aaacd6b34e9cfb03c06588a01bf7d803d4addcf: + missing_count: 1 + reason: Retained label 'repro.py' in docs/services/azure-ai-search/eventual-consistency-reindex/index.md, + pointing to docs/services/azure-ai-search/eventual-consistency-reindex/samples/reproduction/repro.py. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-ai-search/eventual-consistency-reindex/index.md + category: prose + fingerprint: f04da0933ebd55bf75a340f7123745b4e9ee85c4d4817fbff8bf720970a90df2 + count: 1 + - kind: file + path: docs/services/azure-ai-search/eventual-consistency-reindex/samples/reproduction/repro.py + sha256: 2184fc28668b5484826a64aa8a3c4d70d11b449327ac207f0068bd17ab179db8 - baseline_path: aks/affinity.md pages_path: guides/azure-kubernetes-service/pod-affinity-distribution/index.md reviewed_changes: {} @@ -284,18 +633,34 @@ documents: pages_path: guides/azure-kubernetes-service/argocd-image-updater-acr/index.md reviewed_changes: code: - d6b38fb9c0ed840ead39c29f04229843b078bc8a2b54ee798c093a2fb7dc623b: 'In docs/services/azure-kubernetes-service/argocd-image-updater-acr/index.md: - Safety: managed-identity example outputs now use and ; - the troubleshooting imageName example uses acrexamplekrc01.azurecr.io. AcrPull assignment and the - incorrect-tag/correct-no-tag comparison are unchanged. Current code replacement begins: ''# 1. Managed - Identity 생성 az identity create \\ --name id-image-updater-acr \\ --resource-group \\ --location - # 출력에서 client''.' - 6933932059dfc66ed61ccef1aca0b7f81b88893b4ee82c962163db2eccb170fb: 'In docs/services/azure-kubernetes-service/argocd-image-updater-acr/index.md: - Safety: managed-identity example outputs now use and ; - the troubleshooting imageName example uses acrexamplekrc01.azurecr.io. AcrPull assignment and the - incorrect-tag/correct-no-tag comparison are unchanged. Current code replacement begins: ''imageName: - acrexamplekrc01.azurecr.io/test-app:1.0.0 # ❌ imageName: acrexamplekrc01.azurecr.io/test-app # - ✅''.' + d6b38fb9c0ed840ead39c29f04229843b078bc8a2b54ee798c093a2fb7dc623b: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/argocd-image-updater-acr/index.md: Safety: + managed-identity example outputs now use and ; + the troubleshooting imageName example uses acrexamplekrc01.azurecr.io. AcrPull assignment + and the incorrect-tag/correct-no-tag comparison are unchanged. Current code replacement begins: + ''# 1. Managed Identity 생성 az identity create \\ --name id-image-updater-acr \\ --resource-group + \\ --location # 출력에서 client''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/argocd-image-updater-acr/index.md + category: code + fingerprint: d88f43dc096cbb0b0879e33f91165b7039edce5dcca6e770e7158059ec8b803c + count: 1 + 6933932059dfc66ed61ccef1aca0b7f81b88893b4ee82c962163db2eccb170fb: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/argocd-image-updater-acr/index.md: Safety: + managed-identity example outputs now use and ; + the troubleshooting imageName example uses acrexamplekrc01.azurecr.io. AcrPull assignment + and the incorrect-tag/correct-no-tag comparison are unchanged. Current code replacement begins: + ''imageName: acrexamplekrc01.azurecr.io/test-app:1.0.0 # ❌ imageName: acrexamplekrc01.azurecr.io/test-app # + ✅''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/argocd-image-updater-acr/index.md + category: code + fingerprint: 1f006678013d56bbe5a709c6b040e7b58cbd1b2ad1c7869082eed9e8ca9c954f + count: 1 - baseline_path: aks/authorization-troubleshooting.md pages_path: guides/azure-kubernetes-service/authorization-troubleshooting/index.md reviewed_changes: {} @@ -309,17 +674,37 @@ documents: pages_path: cases/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md reviewed_changes: prose: - ca84a9ed13a0e19a9634b801fbd6dda13453618a958b0c47bd195f8cb809ab9f: 'In docs/services/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md: - The overview still records the earlier NFS write-latency capacity fix and the later CPU/I/O bottleneck. - Its filename label was replaced with 파일 I/O 스로틀링 사례 at docs/services/azure-kubernetes-service/file-io-throttling/index.md. - Current prose replacement begins: ''AKS 환경에서 NetApp Files를 사용하는 Node.js 애플리케이션의 트래픽 증가 시 CPU 급증과 - File I/O 대기 현상을 분석한 사례입니다. 이전 용량 증설로 NFS write 지연 문제를 해결했으나(파일 I/O 스로틀링 사례 참조),''.' + ca84a9ed13a0e19a9634b801fbd6dda13453618a958b0c47bd195f8cb809ab9f: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md: The overview + still records the earlier NFS write-latency capacity fix and the later CPU/I/O bottleneck. + Its filename label was replaced with 파일 I/O 스로틀링 사례 at docs/services/azure-kubernetes-service/file-io-throttling/index.md. + Current prose replacement begins: ''AKS 환경에서 NetApp Files를 사용하는 Node.js 애플리케이션의 트래픽 증가 시 CPU + 급증과 File I/O 대기 현상을 분석한 사례입니다. 이전 용량 증설로 NFS write 지연 문제를 해결했으나(파일 I/O 스로틀링 사례 참조),''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md + category: prose + fingerprint: c87e1802dc9ec33f712247a3fc934e275ee0778dcb90f34760a0ab848c28d452 + count: 1 local_link_labels: - 43ee97b242ed9670643e958e64845fe3f91e0f97472db308eeebb2c7efa42dd3: Replaced retired path label with - '파일 I/O 스로틀링 사례' in docs/services/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md, pointing - to docs/services/azure-kubernetes-service/file-io-throttling/index.md. The current canonical source - or owning-sample target was checked; this is a source-link/reader-label migration, not removal of - the referenced content. + 43ee97b242ed9670643e958e64845fe3f91e0f97472db308eeebb2c7efa42dd3: + missing_count: 1 + reason: Replaced retired path label with '파일 I/O 스로틀링 사례' in docs/services/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md, + pointing to docs/services/azure-kubernetes-service/file-io-throttling/index.md. The current + canonical source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md + category: local_link_labels + fingerprint: 88b31595c8f8f7af56271e3cc5e2673e491406aca223c746104bd9d484a9f7f8 + count: 1 + - kind: structure + path: docs/services/azure-kubernetes-service/file-io-throttling/index.md + category: title + fingerprint: e9930620dc993b0893956c55fdcd6b18d4f5eaf134f26cf22ea8089309403f49 + count: 1 - baseline_path: aks/kaito_opensource_model.md pages_path: guides/azure-kubernetes-service/kaito-open-source-model/index.md reviewed_changes: {} @@ -327,111 +712,309 @@ documents: pages_path: guides/azure-kubernetes-service/remote-cluster-local-development/index.md reviewed_changes: prose: - 6c5dd3ad75a6ee04e3c182dd61ce968c821fbbec7353c7acc439ae5beffefeaf: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: - The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder paths - now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. - Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept - ports, feature configuration and cleanup operations remain. Current prose replacement begins: ''예제 - 파일: local-development-loop/''.' - d370a9e4b26a6c8ecb4d0c86183f5e236bb088596a49023f09a0a742ad6b9377: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: - The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder paths - now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. - Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept - ports, feature configuration and cleanup operations remain. Current prose replacement begins: ''- - Azure CLI, kubectl, AKS 클러스터 (예시: aks-example-koreacentral-01 @ rg-example-koreacentral-01)''.' + 6c5dd3ad75a6ee04e3c182dd61ce968c821fbbec7353c7acc439ae5beffefeaf: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: + The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder + paths now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. + Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept + ports, feature configuration and cleanup operations remain. Current prose replacement begins: + ''예제 파일: local-development-loop/''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md + category: prose + fingerprint: 9f3757040e16c6a5b9108dbd776c4e33aaea5902b91fe0455ef283d212bc0008 + count: 1 + d370a9e4b26a6c8ecb4d0c86183f5e236bb088596a49023f09a0a742ad6b9377: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: + The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder + paths now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. + Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept + ports, feature configuration and cleanup operations remain. Current prose replacement begins: + ''- Azure CLI, kubectl, AKS 클러스터 (예시: aks-example-koreacentral-01 @ rg-example-koreacentral-01)''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md + category: prose + fingerprint: 3d46e79be66a08610c89a95885b16fcf91b1afc1064fb562611433ad02b05a90 + count: 1 code: - 3fcb2b20c46c9b60307fb03c47964aa8d4cd4d5ce8fb59e269cee8f7c2a2a9e7: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: - The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder paths - now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. - Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept - ports, feature configuration and cleanup operations remain. Current code replacement begins: ''local-development-loop/ - ├── k8s/echo-service.yaml # tp-demo 네임스페이스 + echo-server Deployment/Service └── local/ ├── local-server.py # - 트''.' - b54eb8ee828f85446be4829817a2bbfe800044daa18d071b396dee2a7bb2b6b0: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: - The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder paths - now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. - Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept - ports, feature configuration and cleanup operations remain. Current code replacement begins: ''az - aks get-credentials -g rg-example-koreacentral-01 -n aks-example-koreacentral-01 kubectl apply -f - docs/services/azure-kubernetes-service/''.' - 7131334823c53fe79909967e4f42d5efa34a2708f6b6da14a3c1c59251ff5c20: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: - The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder paths - now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. - Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept - ports, feature configuration and cleanup operations remain. Current code replacement begins: ''# - Traffic Manager 설치 (클러스터 측, 최초 1회) telepresence helm install # 연결 (macOS: 루트 데몬 때문에 sudo 비밀번호 프롬프트가 - 뜸) telepresence connect # outbound: 로컬''.' - 0e93bf160d5d3cfe00cd225c9b7add6e27d7c37a1a42cbe5be1267c2482083f3: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: - The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder paths - now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. - Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept - ports, feature configuration and cleanup operations remain. Current code replacement begins: ''// - docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/local/mirrord.json - { "target": { "''.' - 08bc7a5774277733446908b524f56ae61c877ac9c39daf00ce1e86c3483fff13: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: - The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder paths - now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. - Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept - ports, feature configuration and cleanup operations remain. Current code replacement begins: ''# - 인바운드 steal + env/dns/fs 미러링 mirrord exec -f docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-developm''.' - 132cad530fddd9189777ee3618042ab55e26b79199ced1e5fc140ff4b28c83bd: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: - The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder paths - now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. - Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept - ports, feature configuration and cleanup operations remain. Current code replacement begins: ''// - .vscode/launch.json { "name": "mirrord demo: local-server (echo-server steal)", "type": "debugpy", - "request": "launch", "program": "${wor''.' - 576beb4e09447093dd5ee0e25c559a64bc8e00812f2383bde770fbd4edb8174d: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: - The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder paths - now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. - Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept - ports, feature configuration and cleanup operations remain. Current code replacement begins: ''# - Telepresence telepresence leave echo-server # intercept 해제 (신버전: detach) telepresence - quit -s # 데몬 종료 telepr''.' + 3fcb2b20c46c9b60307fb03c47964aa8d4cd4d5ce8fb59e269cee8f7c2a2a9e7: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: + The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder + paths now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. + Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept + ports, feature configuration and cleanup operations remain. Current code replacement begins: + ''local-development-loop/ ├── k8s/echo-service.yaml # tp-demo 네임스페이스 + echo-server Deployment/Service + └── local/ ├── local-server.py # 트''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md + category: code + fingerprint: 662b8622b9ff4a5f7b0c2dd893929963c5ed6d03ffdca2d5cdaaf2650f99ded0 + count: 1 + b54eb8ee828f85446be4829817a2bbfe800044daa18d071b396dee2a7bb2b6b0: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: + The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder + paths now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. + Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept + ports, feature configuration and cleanup operations remain. Current code replacement begins: + ''az aks get-credentials -g rg-example-koreacentral-01 -n aks-example-koreacentral-01 kubectl + apply -f docs/services/azure-kubernetes-service/''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md + category: code + fingerprint: aa62d538655c68319c3afde7ffe80d92f5086546032aeaf50648131096a41f94 + count: 1 + 7131334823c53fe79909967e4f42d5efa34a2708f6b6da14a3c1c59251ff5c20: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: + The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder + paths now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. + Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept + ports, feature configuration and cleanup operations remain. Current code replacement begins: + ''# Traffic Manager 설치 (클러스터 측, 최초 1회) telepresence helm install # 연결 (macOS: 루트 데몬 때문에 sudo + 비밀번호 프롬프트가 뜸) telepresence connect # outbound: 로컬''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md + category: code + fingerprint: 2ab41f133603fd51021db930a238b84a6e768e88fa9332cd1bb7d7f77a457120 + count: 1 + 0e93bf160d5d3cfe00cd225c9b7add6e27d7c37a1a42cbe5be1267c2482083f3: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: + The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder + paths now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. + Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept + ports, feature configuration and cleanup operations remain. Current code replacement begins: + ''// docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/local/mirrord.json + { "target": { "''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md + category: code + fingerprint: faab6880107afc2d18236c291ca10e6d32a11036d3959beaebb9bb876859ffec + count: 1 + 08bc7a5774277733446908b524f56ae61c877ac9c39daf00ce1e86c3483fff13: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: + The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder + paths now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. + Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept + ports, feature configuration and cleanup operations remain. Current code replacement begins: + ''# 인바운드 steal + env/dns/fs 미러링 mirrord exec -f docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-developm''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md + category: code + fingerprint: 6a8234e418843bc9f842460c126269653051a632a1f8be557021a3a705fc63da + count: 1 + 132cad530fddd9189777ee3618042ab55e26b79199ced1e5fc140ff4b28c83bd: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: + The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder + paths now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. + Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept + ports, feature configuration and cleanup operations remain. Current code replacement begins: + ''// .vscode/launch.json { "name": "mirrord demo: local-server (echo-server steal)", "type": + "debugpy", "request": "launch", "program": "${wor''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md + category: code + fingerprint: 3c2f9df39bba37ac40d4f816823e4499da9ae8b9b3b172f3704a6d9c0d3ff02b + count: 1 + 576beb4e09447093dd5ee0e25c559a64bc8e00812f2383bde770fbd4edb8174d: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md: + The tree, kubectl apply/delete, Python server, mirrord config/exec and debugpy workspaceFolder + paths now point into docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. + Safety: AKS prerequisites/get-credentials/stop use example AKS and resource-group names. Intercept + ports, feature configuration and cleanup operations remain. Current code replacement begins: + ''# Telepresence telepresence leave echo-server # intercept 해제 (신버전: detach) telepresence + quit -s # 데몬 종료 telepr''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md + category: code + fingerprint: d6ee41c25519bc91cd8afc311f7287a60102c76a8902948d61d41cd81e391838 + count: 1 local_link_labels: - 12b7cd4f2ca28c859ddfcbca8c743e0c1c0344d036a7c9c770a4af743bf45f95: Replaced retired path label with - 'local-development-loop/' in docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md, - pointing to docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. + 12b7cd4f2ca28c859ddfcbca8c743e0c1c0344d036a7c9c770a4af743bf45f95: + missing_count: 1 + reason: Replaced retired path label with 'local-development-loop/' in docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md, + pointing to docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md + category: prose + fingerprint: 9f3757040e16c6a5b9108dbd776c4e33aaea5902b91fe0455ef283d212bc0008 + count: 1 + - kind: file + path: docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/README.md + sha256: 421b904e12e167829e12a5d1f1e5c224e360796db0696e4d345e6a0e894e0176 + - kind: file + path: docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/k8s/echo-service.yaml + sha256: a31170ea1af3c2ccb64cf5c6ec34cad0fe4e29d6da0a5f18570917d204fe2596 + - kind: file + path: docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/local/local-server.py + sha256: 160b1b250a2ab055d1c406befa89c880939ee3bd59cb76b39c2b572ebc48bcf9 + - kind: file + path: docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/local/mirrord.json + sha256: 69468185d6c3ec7a44c7f1f010e2f25d71dbfc76482c491599f9dae225b60b29 + - kind: file + path: docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/sample.yml + sha256: 35a24d4a46f99e284940dd9b6efd777d1f548e826be9f199c6795c42ddc7bfe8 - baseline_path: aks/memray_leak_profiling.md pages_path: guides/azure-kubernetes-service/python-memory-leak-memray/index.md reviewed_changes: prose: - 4445d309dc075ebf3cc910ee5b24c785f8e34227649feb2b8eba5482efb8e1d3: 'In docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md: - The ACR build fence now enters docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling. - The interactive reports remain under its reports/leak-leaks.html and reports/leak-flamegraph.html; - the prose now explicitly instructs downloading and opening those files locally rather than promising - inline HTML rendering. Current prose replacement begins: ''- 원본 HTML 파일(내려받아 브라우저에서 열기): reports/leak-leaks.html - · reports/leak-flamegraph.html''.' + 4445d309dc075ebf3cc910ee5b24c785f8e34227649feb2b8eba5482efb8e1d3: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md: The ACR + build fence now enters docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling. + The interactive reports remain under its reports/leak-leaks.html and reports/leak-flamegraph.html; + the prose now explicitly instructs downloading and opening those files locally rather than + promising inline HTML rendering. Current prose replacement begins: ''- 원본 HTML 파일(내려받아 브라우저에서 + 열기): reports/leak-leaks.html · reports/leak-flamegraph.html''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md + category: prose + fingerprint: 479770d6671f82490cd06fbac4a40702d63cc80cee2be10b0a71e1d217872145 + count: 1 code: - e130e9f9113da3cc95d2e270adf0589443ae2f81e3cb38a05ae4c3d142697564: 'In docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md: - The ACR build fence now enters docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling. - The interactive reports remain under its reports/leak-leaks.html and reports/leak-flamegraph.html; - the prose now explicitly instructs downloading and opening those files locally rather than promising - inline HTML rendering. Current code replacement begins: ''cd docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling - az acr build --registry --image leak''.' + e130e9f9113da3cc95d2e270adf0589443ae2f81e3cb38a05ae4c3d142697564: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md: The ACR + build fence now enters docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling. + The interactive reports remain under its reports/leak-leaks.html and reports/leak-flamegraph.html; + the prose now explicitly instructs downloading and opening those files locally rather than + promising inline HTML rendering. Current code replacement begins: ''cd docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling + az acr build --registry --image leak''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md + category: code + fingerprint: ded0aecec60d7c426645ca108063e2e23ff23463c84626576903c7df1fa30dd4 + count: 1 local_link_labels: - 63298dbc04cec1489c6e9189312de05fb6810646969bde0c35a0092634fe7e7e: Retained label 'debug/Dockerfile' - in docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md, pointing to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/debug/Dockerfile. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 88ce896b3271d19d7d900463556c60803f17ed606892a6fa76187d5b4f4c564b: Retained label 'reports/leak-leaks.html' - in docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md, pointing to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-leaks.html. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - f7e6c7e8504b5d8b79baedd326e64c9dd09a3884626d55b4286a83071b6a0f64: Retained label 'reports/leak-flamegraph.html' - in docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md, pointing to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-flamegraph.html. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - cce3bf06ad38a4d47f1debc2804da8da45849161ea38791f51ffb6227c6ae4ca: Retained label 'memray-leak-profiling/' - in docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md, pointing to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 64c7be708c8f93ffae9396110852cc8d88ffff643ac70c496405c59ca711debd: Retained label 'Dockerfile' in docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md, - pointing to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/Dockerfile. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. + 63298dbc04cec1489c6e9189312de05fb6810646969bde0c35a0092634fe7e7e: + missing_count: 1 + reason: Retained label 'debug/Dockerfile' in docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md, + pointing to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/debug/Dockerfile. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md + category: prose + fingerprint: e1c0a8ceed18b064a4cd6c41e82d44d946ed1cac96a8dc21675c6409472d2f47 + count: 1 + - kind: file + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/debug/Dockerfile + sha256: afd72d820f40468697e146cfba9793872d878ee11b5d0e271f6ff1d0ae248044 + 88ce896b3271d19d7d900463556c60803f17ed606892a6fa76187d5b4f4c564b: + missing_count: 1 + reason: Retained label 'reports/leak-leaks.html' in docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md, + pointing to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-leaks.html. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md + category: prose + fingerprint: 479770d6671f82490cd06fbac4a40702d63cc80cee2be10b0a71e1d217872145 + count: 1 + - kind: file + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-leaks.html + sha256: 6ee1a470eaac5a37c53eb2ac8c13d294d1f0eacc058ba995c15f59a10cb3d89a + f7e6c7e8504b5d8b79baedd326e64c9dd09a3884626d55b4286a83071b6a0f64: + missing_count: 1 + reason: Retained label 'reports/leak-flamegraph.html' in docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md, + pointing to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-flamegraph.html. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md + category: prose + fingerprint: 479770d6671f82490cd06fbac4a40702d63cc80cee2be10b0a71e1d217872145 + count: 1 + - kind: file + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-flamegraph.html + sha256: 989ab827d87d5879ad38013bbc504a2280475b6c1aaadbe95b521b8e19dcbfc5 + cce3bf06ad38a4d47f1debc2804da8da45849161ea38791f51ffb6227c6ae4ca: + missing_count: 1 + reason: Retained label 'memray-leak-profiling/' in docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md, + pointing to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md + category: prose + fingerprint: 39bd9fd637f7d4fb8667658fc6cbc504a5378ed0e465cb507c70e390edca5cd1 + count: 1 + - kind: file + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/Dockerfile + sha256: a6f157d5d5d979095c9f0404c2f8eb7dcc4acf011092b56a9473c0d9e8df48cd + - kind: file + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/README.md + sha256: 375eba4d3966da160fa6bc09571749610c9f7522a70a3f409f803ce6f07b1782 + - kind: file + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/app.py + sha256: 24719b9e16681f5648fcc0792497588309db6cca1c3fd6e049b6634b02e82d3b + - kind: file + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/debug/Dockerfile + sha256: afd72d820f40468697e146cfba9793872d878ee11b5d0e271f6ff1d0ae248044 + - kind: file + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/k8s/leaky-agent.yaml + sha256: d4d0d5a4fc8befdb78a61f38798d7f8f84c58a3b5f1bb0ec144bdbfcb72bfe24 + - kind: file + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/k8s/loadgen.yaml + sha256: 490a6ecfcaffeb4afbe9d57c71786e7da93df16adfb85fd82ab391e150b3b004 + - kind: file + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/loadgen.py + sha256: 3eb97eae48b88529400a528ea6ea74e6bcd50de77472743fb1c37d2aee7f6d81 + - kind: file + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-flamegraph.html + sha256: 989ab827d87d5879ad38013bbc504a2280475b6c1aaadbe95b521b8e19dcbfc5 + - kind: file + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-leaks.html + sha256: 6ee1a470eaac5a37c53eb2ac8c13d294d1f0eacc058ba995c15f59a10cb3d89a + - kind: file + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-leaks.png + sha256: df4e54d5565006d2d54a57ee1d2403c3e67522e49bb64dbdba27bb5c2088d246 + - kind: file + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/requirements.txt + sha256: ed58f33ba0120a0986bdabd19a6be0c75ece9f7187abae199fadc122177a4879 + - kind: file + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/sample.yml + sha256: 0d354f8901fdd8b0830902a135fc3ebbcda9d41eebf595530eee92845f103d85 + 64c7be708c8f93ffae9396110852cc8d88ffff643ac70c496405c59ca711debd: + missing_count: 1 + reason: Retained label 'Dockerfile' in docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md, + pointing to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/Dockerfile. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md + category: prose + fingerprint: 3c5b71ff10a8905c165bac5674b8941cb2dd18d60de275a24b1f1e0d1bf12dc5 + count: 1 + - kind: file + path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/Dockerfile + sha256: a6f157d5d5d979095c9f0404c2f8eb7dcc4acf011092b56a9473c0d9e8df48cd - baseline_path: aks/pod_db_query_latency.md pages_path: cases/azure-kubernetes-service/pod-database-query-latency/index.md reviewed_changes: {} @@ -439,26 +1022,117 @@ documents: pages_path: guides/azure-kubernetes-service/pyroscope-anf-s3/index.md reviewed_changes: local_link_labels: - b5017936d6c2f031f9711c2bd2cff82f1db4c604507ef0de96f491addeca4221: Retained label 'pyroscope-chat-profiling/' - in docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md, pointing to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - d66ec5f5968445f509a55315423ed577736ca1d1d54c36b44c406ae0203af3f1: Retained label 'app.py' in docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md, - pointing to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/app.py. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - b974b045198c87e2e93578433fe9a3c2b3c6c76147ba84c204061456b504a306: Retained label 'deployment.yaml' - in docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md, pointing to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/deployment.yaml. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - a0d74fb7f5bca6aea72cd6899b42ce77135fa30bd556e326b57fbcda776ddd71: Retained label 'deploy.sh' in docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md, - pointing to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/deploy.sh. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - c2af7f80d42e50d1a7508f93267aa137e51fd3d85f2de2798fe712755a74fb88: Retained label 'test.py' in docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md, - pointing to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/test.py. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. + b5017936d6c2f031f9711c2bd2cff82f1db4c604507ef0de96f491addeca4221: + missing_count: 1 + reason: Retained label 'pyroscope-chat-profiling/' in docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md, + pointing to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md + category: prose + fingerprint: 1006bf65943d422efb8b73029add1227cef308f196ac74d6770a3406237fc6d9 + count: 1 + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/Dockerfile + sha256: 3fe1aaf745c9dfeb91a65ad9ca9a9d6f33a5762c8ec2a85e0b582625286914e8 + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/README.md + sha256: f8b278a0f661926f82b9b3d3477c8d88198192d2c45d28289703ddd304b43539 + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/app.py + sha256: acaeb43adfc0dbe20c0a0ed22c4a5fd3d5dd7a5e80a3dee3ba552de83236217b + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/deploy.sh + sha256: 95c380cec713351f9b12d69a74a074b0ccd6d9750f3bbb7092461d8482717748 + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/deployment.yaml + sha256: a185097a2eebb6d50dc1cd04cd43a1ba689002302e45e78b8c0df243d93fe27e + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/images/anf-create-bucket.png + sha256: 5511e6dad7189f545fce5dfabfab5ea91b7e9832a53808c8122a552b097e9932 + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/images/anf-create-certificate.png + sha256: 14521e7c8659c497b6b355ed85e65d18f07cfd98798c1851469bcfe55bd8fb48 + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/images/architecture.excalidraw + sha256: 027663d612ad23e7bf0f04bf96a1b57bcfe1bebbab1cb9cfd4493984a962d87e + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/images/architecture.png + sha256: c8110f8fe0dbc239df469cfff46dc089adf3077f1677514f1839a9f37e2a391a + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/images/pyroscope_ui.png + sha256: 2ecedcf3a40864840d8c4df89d67b840855f4bb07fb2a8b5a7561b0cd7291725 + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/requirements.txt + sha256: b738d94ae2176c2cf8941b8a340096bd58d14e849ca580e7a7ee83e49864ead7 + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/sample.yml + sha256: 76f7c0c54d7f44c2973f238d95a264365a90cdbf5afe53c6527b773cd82e67a4 + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/test.py + sha256: 1b7ec68ad9cc9329b6241d5b00eb679cc43b70902e13f23b2f4eb301c20c51a2 + d66ec5f5968445f509a55315423ed577736ca1d1d54c36b44c406ae0203af3f1: + missing_count: 1 + reason: Retained label 'app.py' in docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md, + pointing to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/app.py. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md + category: prose + fingerprint: 1006bf65943d422efb8b73029add1227cef308f196ac74d6770a3406237fc6d9 + count: 1 + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/app.py + sha256: acaeb43adfc0dbe20c0a0ed22c4a5fd3d5dd7a5e80a3dee3ba552de83236217b + b974b045198c87e2e93578433fe9a3c2b3c6c76147ba84c204061456b504a306: + missing_count: 1 + reason: Retained label 'deployment.yaml' in docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md, + pointing to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/deployment.yaml. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md + category: prose + fingerprint: 1006bf65943d422efb8b73029add1227cef308f196ac74d6770a3406237fc6d9 + count: 1 + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/deployment.yaml + sha256: a185097a2eebb6d50dc1cd04cd43a1ba689002302e45e78b8c0df243d93fe27e + a0d74fb7f5bca6aea72cd6899b42ce77135fa30bd556e326b57fbcda776ddd71: + missing_count: 1 + reason: Retained label 'deploy.sh' in docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md, + pointing to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/deploy.sh. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md + category: prose + fingerprint: 1006bf65943d422efb8b73029add1227cef308f196ac74d6770a3406237fc6d9 + count: 1 + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/deploy.sh + sha256: 95c380cec713351f9b12d69a74a074b0ccd6d9750f3bbb7092461d8482717748 + c2af7f80d42e50d1a7508f93267aa137e51fd3d85f2de2798fe712755a74fb88: + missing_count: 1 + reason: Retained label 'test.py' in docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md, + pointing to docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/test.py. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md + category: prose + fingerprint: 1006bf65943d422efb8b73029add1227cef308f196ac74d6770a3406237fc6d9 + count: 1 + - kind: file + path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/test.py + sha256: 1b7ec68ad9cc9329b6241d5b00eb679cc43b70902e13f23b2f4eb301c20c51a2 - baseline_path: aks/spot_gpu_kaito_llm.md pages_path: guides/azure-kubernetes-service/spot-h100-kaito/index.md reviewed_changes: {} @@ -466,38 +1140,93 @@ documents: pages_path: guides/azure-kubernetes-service/workload-identity-databricks/index.md reviewed_changes: prose: - 5309a8209488f01ec9e373f8ba428a4ffe465b883a52f8325b6afbdb959ee275: 'In docs/services/azure-kubernetes-service/workload-identity-databricks/index.md: - The intro, authentication explanation, optional four-layer architecture and references retain the - Pod-versus-gateway token-attachment distinction. They now link the official APIM authentication-managed-identity - policy. The referenced apim/databricks_keyless_managed_identity.md did not exist in the fixed baseline - Git tree; repairing that dangling reference is not deletion of an owned guide. Current prose replacement - begins: ''시나리오: AKS Pod → Databricks Foundation Model API (databricks-meta-llama-3-1-8b-instruct) - 어떤 PAT/시크릿/Databricks OAuth secret도 사용하지 않고, AKS Wor''.' - 3fbb4c6449456a99caca004fd84f4168c84a0a3e427ec0f4fd26329558455e47: 'In docs/services/azure-kubernetes-service/workload-identity-databricks/index.md: - The intro, authentication explanation, optional four-layer architecture and references retain the - Pod-versus-gateway token-attachment distinction. They now link the official APIM authentication-managed-identity - policy. The referenced apim/databricks_keyless_managed_identity.md did not exist in the fixed baseline - Git tree; repairing that dangling reference is not deletion of an owned guide. Current prose replacement - begins: ''인증 모델은 APIM authentication-managed-identity 정책과 동일합니다. APIM에서는 게이트웨이가 토큰을 받아 붙여주지만, 여기서는 - 앱 코드가 같은 일을 두 줄로 합니다.''.' - 09a9f49ee2c61e7226b2b221eb01146c338d537cb35fc26d005733e080abe893: 'In docs/services/azure-kubernetes-service/workload-identity-databricks/index.md: - The intro, authentication explanation, optional four-layer architecture and references retain the - Pod-versus-gateway token-attachment distinction. They now link the official APIM authentication-managed-identity - policy. The referenced apim/databricks_keyless_managed_identity.md did not exist in the fixed baseline - Git tree; repairing that dangling reference is not deletion of an owned guide. Current prose replacement - begins: ''- 필요하면 Pod 앞에 APIM(Internal VNet)을 추가해 Client → AppGW → APIM → 백엔드의 4계층 패턴을 적용할 수 있습니다. - 이 경우 인증 부착 책임을 APIM authentication-managed-identity ''.' - d502456a7aabecdb82a9f73864fc5352cf8d4d9188deafcae540ca1506f95c48: 'In docs/services/azure-kubernetes-service/workload-identity-databricks/index.md: - The intro, authentication explanation, optional four-layer architecture and references retain the - Pod-versus-gateway token-attachment distinction. They now link the official APIM authentication-managed-identity - policy. The referenced apim/databricks_keyless_managed_identity.md did not exist in the fixed baseline - Git tree; repairing that dangling reference is not deletion of an owned guide. Current prose replacement - begins: ''- 게이트웨이 측 keyless 참고: APIM authentication-managed-identity 정책''.' + 5309a8209488f01ec9e373f8ba428a4ffe465b883a52f8325b6afbdb959ee275: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/workload-identity-databricks/index.md: The + intro, authentication explanation, optional four-layer architecture and references retain + the Pod-versus-gateway token-attachment distinction. They now link the official APIM authentication-managed-identity + policy. The referenced apim/databricks_keyless_managed_identity.md did not exist in the fixed + baseline Git tree; repairing that dangling reference is not deletion of an owned guide. Current + prose replacement begins: ''시나리오: AKS Pod → Databricks Foundation Model API (databricks-meta-llama-3-1-8b-instruct) + 어떤 PAT/시크릿/Databricks OAuth secret도 사용하지 않고, AKS Wor''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/workload-identity-databricks/index.md + category: prose + fingerprint: 61960f199bc73556fadf8ac0b3b6592938d9c1ccdd798a5593f94859635bd6f6 + count: 1 + 3fbb4c6449456a99caca004fd84f4168c84a0a3e427ec0f4fd26329558455e47: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/workload-identity-databricks/index.md: The + intro, authentication explanation, optional four-layer architecture and references retain + the Pod-versus-gateway token-attachment distinction. They now link the official APIM authentication-managed-identity + policy. The referenced apim/databricks_keyless_managed_identity.md did not exist in the fixed + baseline Git tree; repairing that dangling reference is not deletion of an owned guide. Current + prose replacement begins: ''인증 모델은 APIM authentication-managed-identity 정책과 동일합니다. APIM에서는 + 게이트웨이가 토큰을 받아 붙여주지만, 여기서는 앱 코드가 같은 일을 두 줄로 합니다.''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/workload-identity-databricks/index.md + category: prose + fingerprint: e531423c958685a650322e2013634bf61c5ebe6093979893aa53144e4b33f7b7 + count: 1 + 09a9f49ee2c61e7226b2b221eb01146c338d537cb35fc26d005733e080abe893: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/workload-identity-databricks/index.md: The + intro, authentication explanation, optional four-layer architecture and references retain + the Pod-versus-gateway token-attachment distinction. They now link the official APIM authentication-managed-identity + policy. The referenced apim/databricks_keyless_managed_identity.md did not exist in the fixed + baseline Git tree; repairing that dangling reference is not deletion of an owned guide. Current + prose replacement begins: ''- 필요하면 Pod 앞에 APIM(Internal VNet)을 추가해 Client → AppGW → APIM → + 백엔드의 4계층 패턴을 적용할 수 있습니다. 이 경우 인증 부착 책임을 APIM authentication-managed-identity ''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/workload-identity-databricks/index.md + category: prose + fingerprint: 02a34c9f830a85fe5b8579f1004564f5300f50fbb8c593b67226b91b26b8546e + count: 1 + d502456a7aabecdb82a9f73864fc5352cf8d4d9188deafcae540ca1506f95c48: + missing_count: 1 + reason: 'In docs/services/azure-kubernetes-service/workload-identity-databricks/index.md: The + intro, authentication explanation, optional four-layer architecture and references retain + the Pod-versus-gateway token-attachment distinction. They now link the official APIM authentication-managed-identity + policy. The referenced apim/databricks_keyless_managed_identity.md did not exist in the fixed + baseline Git tree; repairing that dangling reference is not deletion of an owned guide. Current + prose replacement begins: ''- 게이트웨이 측 keyless 참고: APIM authentication-managed-identity 정책''.' + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/workload-identity-databricks/index.md + category: prose + fingerprint: 6923c4ffca0b935761c8494001642f0738154b6642aa614c72f2c21018110b07 + count: 1 local_link_labels: - f349de5f189630a6caca82e70e49ac4e78f1cb93e6e83ec9aed097c3a577511b: In docs/services/azure-kubernetes-service/workload-identity-databricks/index.md, - the absent-in-baseline APIM guide reference is replaced by 'APIM authentication-managed-identity - 정책' at https://learn.microsoft.com/azure/api-management/authentication-managed-identity-policy; - the same gateway-versus-Pod token responsibility explanation remains. + f349de5f189630a6caca82e70e49ac4e78f1cb93e6e83ec9aed097c3a577511b: + missing_count: 1 + reason: In docs/services/azure-kubernetes-service/workload-identity-databricks/index.md, the + absent-in-baseline APIM guide reference is replaced by 'APIM authentication-managed-identity + 정책' at https://learn.microsoft.com/azure/api-management/authentication-managed-identity-policy; + the same gateway-versus-Pod token responsibility explanation remains. + evidence: + - kind: structure + path: docs/services/azure-kubernetes-service/workload-identity-databricks/index.md + category: prose + fingerprint: 61960f199bc73556fadf8ac0b3b6592938d9c1ccdd798a5593f94859635bd6f6 + count: 1 + - kind: structure + path: docs/services/azure-kubernetes-service/workload-identity-databricks/index.md + category: prose + fingerprint: e531423c958685a650322e2013634bf61c5ebe6093979893aa53144e4b33f7b7 + count: 1 + - kind: structure + path: docs/services/azure-kubernetes-service/workload-identity-databricks/index.md + category: prose + fingerprint: 02a34c9f830a85fe5b8579f1004564f5300f50fbb8c593b67226b91b26b8546e + count: 1 + - kind: structure + path: docs/services/azure-kubernetes-service/workload-identity-databricks/index.md + category: prose + fingerprint: 6923c4ffca0b935761c8494001642f0738154b6642aa614c72f2c21018110b07 + count: 1 - baseline_path: appgw/sse_response_buffer_disable.md pages_path: guides/azure-application-gateway/sse-response-buffering/index.md reviewed_changes: {} @@ -511,44 +1240,146 @@ documents: pages_path: guides/azure-automation/portal-cli-limitations/index.md reviewed_changes: prose: - 8b131b3a5c426634400f2d55763466f20bbe442683fda9111d15a69b2fdbd061: 'In docs/services/azure-automation/portal-cli-limitations/index.md: - Safety: the 2026-03-08 target note retains its date but uses and rg-example-koreacentral-01 - rather than the operator''s Automation account and resource-group names. Current prose replacement - begins: ''작성일: 2026-03-08 대상: Automation Account (RG: rg-example-koreacentral-01)''.' + 8b131b3a5c426634400f2d55763466f20bbe442683fda9111d15a69b2fdbd061: + missing_count: 1 + reason: 'In docs/services/azure-automation/portal-cli-limitations/index.md: Safety: the 2026-03-08 + target note retains its date but uses and rg-example-koreacentral-01 + rather than the operator''s Automation account and resource-group names. Current prose replacement + begins: ''작성일: 2026-03-08 대상: Automation Account (RG: rg-example-koreacentral-01)''.' + evidence: + - kind: structure + path: docs/services/azure-automation/portal-cli-limitations/index.md + category: prose + fingerprint: 87204d0fc37acf995f5644e2e625867373f5c9bb39763118987c3fa51f186f24 + count: 1 - baseline_path: azureblob/mobile_resumable_upload_tus.md pages_path: guides/azure-storage/mobile-resumable-upload-tus/index.md reviewed_changes: prose: - 41100ddc8ff1b9f5ec0ffc7c22adb27bd6619ef8371daa24c3cc7a0b1894b9b3: 'In docs/services/azure-storage/mobile-resumable-upload-tus/index.md: - The architecture source/sample labels and both build/test-file cd commands now point to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application - (and its scripts subdirectory). Maven, jar, tus behavior and the 50 MB test-file command are retained. - Current prose replacement begins: ''편집 원본: spring-application/architecture.excalidraw''.' - 9b0d2f32c92f7cf4cd02e6a8dbb429fc4bcd9f2d3747c5db07e5aa7877a9f8cb: 'In docs/services/azure-storage/mobile-resumable-upload-tus/index.md: - The architecture source/sample labels and both build/test-file cd commands now point to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application - (and its scripts subdirectory). Maven, jar, tus behavior and the 50 MB test-file command are retained. - Current prose replacement begins: ''샘플 코드 위치: spring-application/''.' + 41100ddc8ff1b9f5ec0ffc7c22adb27bd6619ef8371daa24c3cc7a0b1894b9b3: + missing_count: 1 + reason: 'In docs/services/azure-storage/mobile-resumable-upload-tus/index.md: The architecture + source/sample labels and both build/test-file cd commands now point to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application + (and its scripts subdirectory). Maven, jar, tus behavior and the 50 MB test-file command are + retained. Current prose replacement begins: ''편집 원본: spring-application/architecture.excalidraw''.' + evidence: + - kind: structure + path: docs/services/azure-storage/mobile-resumable-upload-tus/index.md + category: prose + fingerprint: a6aa91987c3f30969d2b3494e1b2b1d3f30a30fcde4b229885a03a38705a9a46 + count: 1 + 9b0d2f32c92f7cf4cd02e6a8dbb429fc4bcd9f2d3747c5db07e5aa7877a9f8cb: + missing_count: 1 + reason: 'In docs/services/azure-storage/mobile-resumable-upload-tus/index.md: The architecture + source/sample labels and both build/test-file cd commands now point to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application + (and its scripts subdirectory). Maven, jar, tus behavior and the 50 MB test-file command are + retained. Current prose replacement begins: ''샘플 코드 위치: spring-application/''.' + evidence: + - kind: structure + path: docs/services/azure-storage/mobile-resumable-upload-tus/index.md + category: prose + fingerprint: daa04b206085e46153c3f3898b9846607cd3b0c40013491c3f8c930587067040 + count: 1 code: - 25753c58d20f9fba6bbcce5db5b44878f77b6b8a2feb80744cd858df341c8e4a: 'In docs/services/azure-storage/mobile-resumable-upload-tus/index.md: - The architecture source/sample labels and both build/test-file cd commands now point to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application - (and its scripts subdirectory). Maven, jar, tus behavior and the 50 MB test-file command are retained. - Current code replacement begins: ''cd docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application - mvn -DskipTests package java -jar target/spring-resuma''.' - cf497682b5cd127d447220829e8848deb8492c10f1e8bf7e6635164dcd0e8bf0: 'In docs/services/azure-storage/mobile-resumable-upload-tus/index.md: - The architecture source/sample labels and both build/test-file cd commands now point to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application - (and its scripts subdirectory). Maven, jar, tus behavior and the 50 MB test-file command are retained. - Current code replacement begins: ''cd docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/scripts - ./make-test-file.sh test-50mb.bin 50''.' + 25753c58d20f9fba6bbcce5db5b44878f77b6b8a2feb80744cd858df341c8e4a: + missing_count: 1 + reason: 'In docs/services/azure-storage/mobile-resumable-upload-tus/index.md: The architecture + source/sample labels and both build/test-file cd commands now point to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application + (and its scripts subdirectory). Maven, jar, tus behavior and the 50 MB test-file command are + retained. Current code replacement begins: ''cd docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application + mvn -DskipTests package java -jar target/spring-resuma''.' + evidence: + - kind: structure + path: docs/services/azure-storage/mobile-resumable-upload-tus/index.md + category: code + fingerprint: 81b1659e83ccdb05fda938ed24f98746e4e13a23b49e95db8edfa7d438c53d46 + count: 1 + cf497682b5cd127d447220829e8848deb8492c10f1e8bf7e6635164dcd0e8bf0: + missing_count: 1 + reason: 'In docs/services/azure-storage/mobile-resumable-upload-tus/index.md: The architecture + source/sample labels and both build/test-file cd commands now point to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application + (and its scripts subdirectory). Maven, jar, tus behavior and the 50 MB test-file command are + retained. Current code replacement begins: ''cd docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/scripts + ./make-test-file.sh test-50mb.bin 50''.' + evidence: + - kind: structure + path: docs/services/azure-storage/mobile-resumable-upload-tus/index.md + category: code + fingerprint: 53009d6f818a645a09082c607cf9d95f6404f444d260cde8ceea6cd91a4d6747 + count: 1 local_link_labels: - 5ed8f73e935ac3d1e2ccd8649c10e09062ba686fa92993b68390bf44f930bfce: Replaced retired path label with - 'spring-application/architecture.excalidraw' in docs/services/azure-storage/mobile-resumable-upload-tus/index.md, - pointing to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/architecture.excalidraw. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 8029079ca642dbd6a81ba1d4c010793d347d31af7b12942eef74073216953c8f: Replaced retired path label with - 'spring-application/' in docs/services/azure-storage/mobile-resumable-upload-tus/index.md, pointing - to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application. The current - canonical source or owning-sample target was checked; this is a source-link/reader-label migration, - not removal of the referenced content. + 5ed8f73e935ac3d1e2ccd8649c10e09062ba686fa92993b68390bf44f930bfce: + missing_count: 1 + reason: Replaced retired path label with 'spring-application/architecture.excalidraw' in docs/services/azure-storage/mobile-resumable-upload-tus/index.md, + pointing to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/architecture.excalidraw. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-storage/mobile-resumable-upload-tus/index.md + category: prose + fingerprint: a6aa91987c3f30969d2b3494e1b2b1d3f30a30fcde4b229885a03a38705a9a46 + count: 1 + - kind: file + path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/architecture.excalidraw + sha256: 44032168d2c735c4472cdb05d28b15de2d8d0577cf8c40b86ef645eb3db83ff2 + 8029079ca642dbd6a81ba1d4c010793d347d31af7b12942eef74073216953c8f: + missing_count: 1 + reason: Replaced retired path label with 'spring-application/' in docs/services/azure-storage/mobile-resumable-upload-tus/index.md, + pointing to docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-storage/mobile-resumable-upload-tus/index.md + category: prose + fingerprint: daa04b206085e46153c3f3898b9846607cd3b0c40013491c3f8c930587067040 + count: 1 + - kind: file + path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/.gitignore + sha256: a46232e6737f10f230bc70c29caa8f510d8eaf14902cb92dccda2ab5969bf330 + - kind: file + path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/README.md + sha256: 05eca1accc833acd567328e6a22fbbb7cb39a050838bfd43d30c84538ed4af7f + - kind: file + path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/architecture.excalidraw + sha256: 44032168d2c735c4472cdb05d28b15de2d8d0577cf8c40b86ef645eb3db83ff2 + - kind: file + path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/architecture.png + sha256: fac4f65b9a9bca9c22770b835be464d824e1d350496fbb8015ef7890bf70b344 + - kind: file + path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/pom.xml + sha256: 4e8e97b399de9cc0a761b8898a010d71c08155841481f71a050fc0bc7b4a821f + - kind: file + path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/sample.yml + sha256: 0085dd88156710cacda0569a1dcccf7ec481f3404ae42510dafc1c4349f8b2a7 + - kind: file + path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/scripts/make-test-file.sh + sha256: dcc2519edf53a0a7b878daccfa2245d71b28456b337414837bb65da6c2837145 + - kind: file + path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/scripts/tus_client.py + sha256: 5c7ac76e32634d6b380c6a97cf1434470262cbc307149c87ac13ee56dfccab2e + - kind: file + path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/scripts/verify-blob.sh + sha256: 2ad00d660c8608059a970e4a747fd148eae688ecd9a99c6b3794e733831c16ff + - kind: file + path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/java/com/example/upload/UploadApplication.java + sha256: 811ea45d8cdc38ed7ccf195d61066a5170730bbfbc7a45d9fb1eeee4daf4c7bd + - kind: file + path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/java/com/example/upload/config/BlobConfig.java + sha256: 8930ef51e329a44ff97e55ab08bdbb401c3e9de18aea001c7c9a612c81be4620 + - kind: file + path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/java/com/example/upload/tus/TusController.java + sha256: 3146d39536347bc7191565cc454c670e285d05cbf6f234d27bdf998ea24ee4d0 + - kind: file + path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/java/com/example/upload/tus/TusFilter.java + sha256: 54af1ddb6c6a352a9755bc239a0cb267fde28cab44678c73eaf4161fc7e933c9 + - kind: file + path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/java/com/example/upload/tus/TusUploadService.java + sha256: 69c96086d55e83bf5d014bde46917d52e67b658931f2940ea0acf6db334bf66d + - kind: file + path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/resources/application.yml + sha256: dd03e866d03037a3e0da5a4dd75768bc8ff669cfd80e4ec10b1d8a38867ea318 - baseline_path: cosmosdb/nodejs/client.md pages_path: guides/azure-cosmos-db/nodejs-client-optimization/index.md reviewed_changes: {} @@ -559,11 +1390,19 @@ documents: pages_path: guides/azure-cosmos-db/nodejs-dns-lookup-bottleneck/index.md reviewed_changes: code: - ffa3cac31015cbd346a0420d939ce942d2414671086d1447b0ababc6bbde0f4f: 'In docs/services/azure-cosmos-db/nodejs-dns-lookup-bottleneck/index.md: - Safety: the CacheableLookup/http.get example uses cosmos-example-northeurope.documents.azure.com - rather than the production Cosmos account hostname. The caching installation, request and response - logging remain. Current code replacement begins: "const http = require(''http''); const CacheableLookup - = require(''cacheable-lookup''); const cacheable = new CacheableLookup(); cacheable.instal".' + ffa3cac31015cbd346a0420d939ce942d2414671086d1447b0ababc6bbde0f4f: + missing_count: 1 + reason: 'In docs/services/azure-cosmos-db/nodejs-dns-lookup-bottleneck/index.md: Safety: the + CacheableLookup/http.get example uses cosmos-example-northeurope.documents.azure.com rather + than the production Cosmos account hostname. The caching installation, request and response + logging remain. Current code replacement begins: "const http = require(''http''); const CacheableLookup + = require(''cacheable-lookup''); const cacheable = new CacheableLookup(); cacheable.instal".' + evidence: + - kind: structure + path: docs/services/azure-cosmos-db/nodejs-dns-lookup-bottleneck/index.md + category: code + fingerprint: 5d54b7280b631e6dee37418c44c982b8eaa6244f3036e594da7e1e0ab96a4ec0 + count: 1 - baseline_path: develop/nodejs/fileio_issue_cpu_overhead.md pages_path: guides/application-development/nodejs-file-io-cpu/index.md reviewed_changes: {} @@ -571,48 +1410,113 @@ documents: pages_path: research/azure-hdinsight/kafka-catchup-sku-fetch-benchmark/index.md reviewed_changes: prose: - 6a7e1e6343d629bc301f15d232a9cdfe9a5be928406dbe6f1a3289a272bc40e6: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md: - Safety: the dated 2026-07-16 cluster note, teardown note and deletion fence use hdi-example-japaneast-01 - and rg-example-japaneast-01. The related-document bullets now name the canonical monitoring overview - and prometheus-grafana child. Broker SKUs/counts, fetch-size results, timestamps and teardown history - are retained. Current prose replacement begins: ''- Date: 2026-07-16 | Region: japaneast | Cluster: - hdi-example-japaneast-01 (HDInsight Kafka, broker 3)''.' - 4068e7da34b27a234109d61cb752f3009a2cdfdc93541276f753943f6b85a9ac: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md: - Safety: the dated 2026-07-16 cluster note, teardown note and deletion fence use hdi-example-japaneast-01 - and rg-example-japaneast-01. The related-document bullets now name the canonical monitoring overview - and prometheus-grafana child. Broker SKUs/counts, fetch-size results, timestamps and teardown history - are retained. Current prose replacement begins: ''- Cluster hdi-example-japaneast-01: deleted (D4·D8·D16 - 각 테스트 후 재생성/삭제).''.' - 83dd9a22cda4f857e76f175179141b2d19ede1a412dba47e14fee6dad86b4c2e: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md: - Safety: the dated 2026-07-16 cluster note, teardown note and deletion fence use hdi-example-japaneast-01 - and rg-example-japaneast-01. The related-document bullets now name the canonical monitoring overview - and prometheus-grafana child. Broker SKUs/counts, fetch-size results, timestamps and teardown history - are retained. Current prose replacement begins: ''- HDInsight Kafka 모니터링 방식 비교 — Azure Monitor·Log - Analytics·Ambari·진단 설정 개요.''.' - 78f3c56debcb02da5e0b098c5b12319ed6ae8b47e0d9acbd48e46904bc7a73bf: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md: - Safety: the dated 2026-07-16 cluster note, teardown note and deletion fence use hdi-example-japaneast-01 - and rg-example-japaneast-01. The related-document bullets now name the canonical monitoring overview - and prometheus-grafana child. Broker SKUs/counts, fetch-size results, timestamps and teardown history - are retained. Current prose replacement begins: ''- Prometheus + Grafana 대시보드 구성 — broker JMX·kafka-exporter(partition별 - lag) 대시보드 구성.''.' + 6a7e1e6343d629bc301f15d232a9cdfe9a5be928406dbe6f1a3289a272bc40e6: + missing_count: 1 + reason: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md: + Safety: the dated 2026-07-16 cluster note, teardown note and deletion fence use hdi-example-japaneast-01 + and rg-example-japaneast-01. The related-document bullets now name the canonical monitoring + overview and prometheus-grafana child. Broker SKUs/counts, fetch-size results, timestamps + and teardown history are retained. Current prose replacement begins: ''- Date: 2026-07-16 + | Region: japaneast | Cluster: hdi-example-japaneast-01 (HDInsight Kafka, broker 3)''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md + category: prose + fingerprint: 3ffb0ed3985e90026d6fb67f6ec9d56cda63c48a695eff6f5d221a2cb464264d + count: 1 + 4068e7da34b27a234109d61cb752f3009a2cdfdc93541276f753943f6b85a9ac: + missing_count: 1 + reason: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md: + Safety: the dated 2026-07-16 cluster note, teardown note and deletion fence use hdi-example-japaneast-01 + and rg-example-japaneast-01. The related-document bullets now name the canonical monitoring + overview and prometheus-grafana child. Broker SKUs/counts, fetch-size results, timestamps + and teardown history are retained. Current prose replacement begins: ''- Cluster hdi-example-japaneast-01: + deleted (D4·D8·D16 각 테스트 후 재생성/삭제).''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md + category: prose + fingerprint: b462a5530ca3e3c3cf593c942a163968decb47ee346bfebe69f70adfa8032722 + count: 1 + 83dd9a22cda4f857e76f175179141b2d19ede1a412dba47e14fee6dad86b4c2e: + missing_count: 1 + reason: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md: + Safety: the dated 2026-07-16 cluster note, teardown note and deletion fence use hdi-example-japaneast-01 + and rg-example-japaneast-01. The related-document bullets now name the canonical monitoring + overview and prometheus-grafana child. Broker SKUs/counts, fetch-size results, timestamps + and teardown history are retained. Current prose replacement begins: ''- HDInsight Kafka 모니터링 + 방식 비교 — Azure Monitor·Log Analytics·Ambari·진단 설정 개요.''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md + category: prose + fingerprint: d40a8efc81403209a12054ba7c1800aca8befd84e95ab7a94316ef30f1d4dad3 + count: 1 + 78f3c56debcb02da5e0b098c5b12319ed6ae8b47e0d9acbd48e46904bc7a73bf: + missing_count: 1 + reason: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md: + Safety: the dated 2026-07-16 cluster note, teardown note and deletion fence use hdi-example-japaneast-01 + and rg-example-japaneast-01. The related-document bullets now name the canonical monitoring + overview and prometheus-grafana child. Broker SKUs/counts, fetch-size results, timestamps + and teardown history are retained. Current prose replacement begins: ''- Prometheus + Grafana + 대시보드 구성 — broker JMX·kafka-exporter(partition별 lag) 대시보드 구성.''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md + category: prose + fingerprint: 51e59cbc416449dfe0f8df1ef8694e95164831e646b202c6bc7d563f28d604ca + count: 1 code: - e5a3880602f6c5d7fa163ef51ab92a8038f6fa8881fbcb752c05f17e5a977106: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md: - Safety: the dated 2026-07-16 cluster note, teardown note and deletion fence use hdi-example-japaneast-01 - and rg-example-japaneast-01. The related-document bullets now name the canonical monitoring overview - and prometheus-grafana child. Broker SKUs/counts, fetch-size results, timestamps and teardown history - are retained. Current code replacement begins: ''az hdinsight delete -g rg-example-japaneast-01 - -n hdi-example-japaneast-01 --yes # 전체 정리: az group delete -n rg-example-japaneast-01 --yes''.' + e5a3880602f6c5d7fa163ef51ab92a8038f6fa8881fbcb752c05f17e5a977106: + missing_count: 1 + reason: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md: + Safety: the dated 2026-07-16 cluster note, teardown note and deletion fence use hdi-example-japaneast-01 + and rg-example-japaneast-01. The related-document bullets now name the canonical monitoring + overview and prometheus-grafana child. Broker SKUs/counts, fetch-size results, timestamps + and teardown history are retained. Current code replacement begins: ''az hdinsight delete + -g rg-example-japaneast-01 -n hdi-example-japaneast-01 --yes # 전체 정리: az group delete -n rg-example-japaneast-01 + --yes''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md + category: code + fingerprint: 5d3c3a32e4a048f553a253de78dd04da1c0a2d7db72aac079f528716d9610bdd + count: 1 local_link_labels: - 6b3c9691462574bae113e8fd71be07ec943e835a621e14bb9c459ae05d5c4df8: Replaced retired path label with - 'HDInsight Kafka 모니터링 방식 비교' in docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md, - pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md. The current canonical - source or owning-sample target was checked; this is a source-link/reader-label migration, not removal - of the referenced content. - 1a367b94c351d88a8178eab696e123992e8ddec22279845c41be4c9c8d8ba89d: Replaced retired path label with - 'Prometheus + Grafana 대시보드 구성' in docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md, - pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. + 6b3c9691462574bae113e8fd71be07ec943e835a621e14bb9c459ae05d5c4df8: + missing_count: 1 + reason: Replaced retired path label with 'HDInsight Kafka 모니터링 방식 비교' in docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md, + pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md. The current canonical + source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md + category: local_link_labels + fingerprint: a4bddffc5fe27ac29dee1db3247764f0bcd4add39b2363df11e3c32386124149 + count: 1 + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md + category: title + fingerprint: 6b2f2e2b757771a85135aab52ed8f6f23cb7c1f61c01a4792c1b57fd5edd6ad0 + count: 1 + 1a367b94c351d88a8178eab696e123992e8ddec22279845c41be4c9c8d8ba89d: + missing_count: 1 + reason: Replaced retired path label with 'Prometheus + Grafana 대시보드 구성' in docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md, + pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md + category: local_link_labels + fingerprint: 86e83c386fa53d20ab065fd3469fd60fbbad27dbca3e7cefc13615610e567543 + count: 1 + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md + category: title + fingerprint: 8a3b50c0fb8014d577ca4609aeb5e8d1d7b0c2b6b3892893a5124296ca8f5b5b + count: 1 - baseline_path: loadtest/loadtest_locust_appgw_aks.md pages_path: guides/azure-load-testing/locust-appgw-aks-private/index.md reviewed_changes: {} @@ -638,101 +1542,383 @@ documents: pages_path: research/microsoft-foundry/agent-memory-overview/index.md reviewed_changes: headings: - 835522e41b3ff8bcf45c9484a90e83e402d787f26f4a75af836ed16205962cad: Restored in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md - section "Historical reader map (pre-Pages)" with the complete six-row audience map; the canonical - 목적별 빠른 경로 section links to this historical evidence. - a616717ab9bb843dd1e9b29a72135382fdf79033698f9646baced38daffb9241: Renamed to section "목적별 빠른 경로" in - docs/services/microsoft-foundry/agent-memory/index.md; the time-limited, design-first and implementation-first - reading paths are unchanged. + 835522e41b3ff8bcf45c9484a90e83e402d787f26f4a75af836ed16205962cad: + missing_count: 1 + reason: Restored in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + section "Historical reader map (pre-Pages)" with the complete six-row audience map; the canonical + 목적별 빠른 경로 section links to this historical evidence. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: headings + fingerprint: 835522e41b3ff8bcf45c9484a90e83e402d787f26f4a75af836ed16205962cad + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: prose + fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 + count: 1 + a616717ab9bb843dd1e9b29a72135382fdf79033698f9646baced38daffb9241: + missing_count: 1 + reason: Renamed to section "목적별 빠른 경로" in docs/services/microsoft-foundry/agent-memory/index.md; + the time-limited, design-first and implementation-first reading paths are unchanged. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: headings + fingerprint: 953992b9d73a7826c86badf503e96b07aca4be44c50e5887573f5004e4581e8c + count: 1 tables: - 6e38615f1cdfdd39f0aea0030a93f746e1c1578c803a4b72debcacc90d244c25: Restored exact header/data row in - docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md section "문서 구성" - beneath the historical-map disclaimer, with canonical child links. Generated topic navigation had - omitted these detailed reading objectives and audience assignments. - 93c27befa80b335ea9d8d8d70ca85310dd3cb26f8007eaa75464a25f6c99454d: Restored exact header/data row in - docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md section "문서 구성" - beneath the historical-map disclaimer, with canonical child links. Generated topic navigation had - omitted these detailed reading objectives and audience assignments. - bb310d837851c9f8194c24e845aa8165eea897ab59daaf49b7932e491fb38c51: Restored exact header/data row in - docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md section "문서 구성" - beneath the historical-map disclaimer, with canonical child links. Generated topic navigation had - omitted these detailed reading objectives and audience assignments. - 2013c3ede52cd3f5bbc066a7c88e6c3ae08e0ab49008c0a7a0c39f95085f7708: Restored exact header/data row in - docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md section "문서 구성" - beneath the historical-map disclaimer, with canonical child links. Generated topic navigation had - omitted these detailed reading objectives and audience assignments. - 1210072435224aaf485a3771382f25c40414924bee56946ae7235373bc6a64fa: Restored exact header/data row in - docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md section "문서 구성" - beneath the historical-map disclaimer, with canonical child links. Generated topic navigation had - omitted these detailed reading objectives and audience assignments. - 2965b3b159e353b6284373789503745ad90499bcd8b641e6c6fc66b39214408f: Restored exact header/data row in - docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md section "문서 구성" - beneath the historical-map disclaimer, with canonical child links. Generated topic navigation had - omitted these detailed reading objectives and audience assignments. - b161a4f98ec2067c6e78b388a7631bb35b87ca64254f845d5246223793bd7257: Restored exact header/data row in - docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md section "문서 구성" - beneath the historical-map disclaimer, with canonical child links. Generated topic navigation had - omitted these detailed reading objectives and audience assignments. + 6e38615f1cdfdd39f0aea0030a93f746e1c1578c803a4b72debcacc90d244c25: + missing_count: 1 + reason: Restored exact header/data row in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + section "문서 구성" beneath the historical-map disclaimer, with canonical child links. Generated + topic navigation had omitted these detailed reading objectives and audience assignments. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: tables + fingerprint: 6e38615f1cdfdd39f0aea0030a93f746e1c1578c803a4b72debcacc90d244c25 + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: prose + fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 + count: 1 + 93c27befa80b335ea9d8d8d70ca85310dd3cb26f8007eaa75464a25f6c99454d: + missing_count: 1 + reason: Restored exact header/data row in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + section "문서 구성" beneath the historical-map disclaimer, with canonical child links. Generated + topic navigation had omitted these detailed reading objectives and audience assignments. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: tables + fingerprint: 93c27befa80b335ea9d8d8d70ca85310dd3cb26f8007eaa75464a25f6c99454d + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: prose + fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 + count: 1 + bb310d837851c9f8194c24e845aa8165eea897ab59daaf49b7932e491fb38c51: + missing_count: 1 + reason: Restored exact header/data row in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + section "문서 구성" beneath the historical-map disclaimer, with canonical child links. Generated + topic navigation had omitted these detailed reading objectives and audience assignments. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: tables + fingerprint: bb310d837851c9f8194c24e845aa8165eea897ab59daaf49b7932e491fb38c51 + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: prose + fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 + count: 1 + 2013c3ede52cd3f5bbc066a7c88e6c3ae08e0ab49008c0a7a0c39f95085f7708: + missing_count: 1 + reason: Restored exact header/data row in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + section "문서 구성" beneath the historical-map disclaimer, with canonical child links. Generated + topic navigation had omitted these detailed reading objectives and audience assignments. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: tables + fingerprint: 2013c3ede52cd3f5bbc066a7c88e6c3ae08e0ab49008c0a7a0c39f95085f7708 + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: prose + fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 + count: 1 + 1210072435224aaf485a3771382f25c40414924bee56946ae7235373bc6a64fa: + missing_count: 1 + reason: Restored exact header/data row in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + section "문서 구성" beneath the historical-map disclaimer, with canonical child links. Generated + topic navigation had omitted these detailed reading objectives and audience assignments. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: tables + fingerprint: 1210072435224aaf485a3771382f25c40414924bee56946ae7235373bc6a64fa + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: prose + fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 + count: 1 + 2965b3b159e353b6284373789503745ad90499bcd8b641e6c6fc66b39214408f: + missing_count: 1 + reason: Restored exact header/data row in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + section "문서 구성" beneath the historical-map disclaimer, with canonical child links. Generated + topic navigation had omitted these detailed reading objectives and audience assignments. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: tables + fingerprint: 2965b3b159e353b6284373789503745ad90499bcd8b641e6c6fc66b39214408f + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: prose + fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 + count: 1 + b161a4f98ec2067c6e78b388a7631bb35b87ca64254f845d5246223793bd7257: + missing_count: 1 + reason: Restored exact header/data row in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + section "문서 구성" beneath the historical-map disclaimer, with canonical child links. Generated + topic navigation had omitted these detailed reading objectives and audience assignments. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: tables + fingerprint: b161a4f98ec2067c6e78b388a7631bb35b87ca64254f845d5246223793bd7257 + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: prose + fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 + count: 1 local_link_labels: - 6e0523469bc57a9e724a9b332a11bec54fd18dfaebc9b1d7609bef38f3421e6a: Restored label '01. 메모리 분류 체계' in - docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md section "문서 구성", - linking to docs/services/microsoft-foundry/agent-memory/taxonomy/index.md. The original detailed - audience map remains explicitly historical and is linked from the canonical guide. - 8d4cbf9cdc1e35d919f0900882f792fa23f92052eac2647f749a6d41c54e9045: Restored label '02. 아키텍처 패턴' in - docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md section "문서 구성", - linking to docs/services/microsoft-foundry/agent-memory/architecture-patterns/index.md. The original - detailed audience map remains explicitly historical and is linked from the canonical guide. - ac95bcf8bf57be7eb83236b143401db16a4a998d6b5e689ad646a8774e57f5c3: Restored label '04. 프레임워크 비교' in - docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md section "문서 구성", - linking to docs/services/microsoft-foundry/agent-memory/frameworks/index.md. The original detailed - audience map remains explicitly historical and is linked from the canonical guide. - 10d1e473e9ebe8c05b3c51bb0150eb66e3c7f8e4318b549c32577c2d208c4099: Restored label '05. 프로덕션과 평가' in - docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md section "문서 구성", - linking to docs/services/microsoft-foundry/agent-memory/production-evaluation/index.md. The original - detailed audience map remains explicitly historical and is linked from the canonical guide. - ee708ebb80e2486837892f2629a6ab654c815a44e5216e406b5d9894e1044571: Retained label 'images/01-memory-taxonomy.svg' - in docs/services/microsoft-foundry/agent-memory/index.md, pointing to docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/01-memory-taxonomy.svg. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 11a1f30b2750cc2cacdf6a469f4db322fba63e0a01c49f097bb0003a0b7777ee: Retained label 'images/02-architecture-patterns.svg' - in docs/services/microsoft-foundry/agent-memory/index.md, pointing to docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/02-architecture-patterns.svg. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - db83ec5f48f147e309ada3539b4cb341f1eac05606f8e063f97c4b3528e26a5c: Retained label 'images/03-memory-pipeline.svg' - in docs/services/microsoft-foundry/agent-memory/index.md, pointing to docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/03-memory-pipeline.svg. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 9e5f8f51ef96fa84bc4552892a69cdfc89d8f94a5ef1a822ceabeddf02e35464: Retained label 'images/04-production-tiers.svg' - in docs/services/microsoft-foundry/agent-memory/index.md, pointing to docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/04-production-tiers.svg. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - fe2b3e96ca22941642e539d494b3861481ebfc8abc5a54ec0a1b5705f21b25b9: Retained label 'images/05-commerce-blueprint.svg' - in docs/services/microsoft-foundry/agent-memory/index.md, pointing to docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/05-commerce-blueprint.svg. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - e545fbfd8a6db9a606f96c20118846de2fb6ce95bd45a6a6c9707adb282da640: Restored label '06. 커머스 적용 설계' in - docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md section "문서 구성", - linking to docs/services/microsoft-foundry/agent-memory/commerce/index.md. The original detailed - audience map remains explicitly historical and is linked from the canonical guide. - a63a178680757909d7b1cb6ad4e67e89420ea1882685f828f240218517493cb2: Restored label '03. 파이프라인과 검색' in - docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md section "문서 구성", - linking to docs/services/microsoft-foundry/agent-memory/pipeline-retrieval/index.md. The original - detailed audience map remains explicitly historical and is linked from the canonical guide. + 6e0523469bc57a9e724a9b332a11bec54fd18dfaebc9b1d7609bef38f3421e6a: + missing_count: 1 + reason: Restored label '01. 메모리 분류 체계' in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + section "문서 구성", linking to docs/services/microsoft-foundry/agent-memory/taxonomy/index.md. + The original detailed audience map remains explicitly historical and is linked from the canonical + guide. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: local_link_labels + fingerprint: 6e0523469bc57a9e724a9b332a11bec54fd18dfaebc9b1d7609bef38f3421e6a + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: tables + fingerprint: 93c27befa80b335ea9d8d8d70ca85310dd3cb26f8007eaa75464a25f6c99454d + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: prose + fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 + count: 1 + 8d4cbf9cdc1e35d919f0900882f792fa23f92052eac2647f749a6d41c54e9045: + missing_count: 1 + reason: Restored label '02. 아키텍처 패턴' in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + section "문서 구성", linking to docs/services/microsoft-foundry/agent-memory/architecture-patterns/index.md. + The original detailed audience map remains explicitly historical and is linked from the canonical + guide. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: local_link_labels + fingerprint: 8d4cbf9cdc1e35d919f0900882f792fa23f92052eac2647f749a6d41c54e9045 + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: tables + fingerprint: bb310d837851c9f8194c24e845aa8165eea897ab59daaf49b7932e491fb38c51 + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: prose + fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 + count: 1 + ac95bcf8bf57be7eb83236b143401db16a4a998d6b5e689ad646a8774e57f5c3: + missing_count: 1 + reason: Restored label '04. 프레임워크 비교' in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + section "문서 구성", linking to docs/services/microsoft-foundry/agent-memory/frameworks/index.md. + The original detailed audience map remains explicitly historical and is linked from the canonical + guide. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: local_link_labels + fingerprint: ac95bcf8bf57be7eb83236b143401db16a4a998d6b5e689ad646a8774e57f5c3 + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: tables + fingerprint: 1210072435224aaf485a3771382f25c40414924bee56946ae7235373bc6a64fa + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: prose + fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 + count: 1 + 10d1e473e9ebe8c05b3c51bb0150eb66e3c7f8e4318b549c32577c2d208c4099: + missing_count: 1 + reason: Restored label '05. 프로덕션과 평가' in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + section "문서 구성", linking to docs/services/microsoft-foundry/agent-memory/production-evaluation/index.md. + The original detailed audience map remains explicitly historical and is linked from the canonical + guide. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: local_link_labels + fingerprint: 10d1e473e9ebe8c05b3c51bb0150eb66e3c7f8e4318b549c32577c2d208c4099 + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: tables + fingerprint: 2965b3b159e353b6284373789503745ad90499bcd8b641e6c6fc66b39214408f + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: prose + fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 + count: 1 + ee708ebb80e2486837892f2629a6ab654c815a44e5216e406b5d9894e1044571: + missing_count: 1 + reason: Retained label 'images/01-memory-taxonomy.svg' in docs/services/microsoft-foundry/agent-memory/index.md, + pointing to docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/01-memory-taxonomy.svg. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: tables + fingerprint: a9100cd68afb54137c8011ff760922aef7458c8fbf8cadb40e8a072ce9bec391 + count: 1 + - kind: file + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/01-memory-taxonomy.svg + sha256: 05597176b2cddfae85b1db975a76d9a55b294256e08cbad2cd3041557494369c + 11a1f30b2750cc2cacdf6a469f4db322fba63e0a01c49f097bb0003a0b7777ee: + missing_count: 1 + reason: Retained label 'images/02-architecture-patterns.svg' in docs/services/microsoft-foundry/agent-memory/index.md, + pointing to docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/02-architecture-patterns.svg. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: tables + fingerprint: 6015cdac73fed89d3668aea50442cffd1138f327dded478b978f56063729ab85 + count: 1 + - kind: file + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/02-architecture-patterns.svg + sha256: 3d956a3c082a022066c0a728ca71cfee06a5836b4000d7684e8675e26c2bce84 + db83ec5f48f147e309ada3539b4cb341f1eac05606f8e063f97c4b3528e26a5c: + missing_count: 1 + reason: Retained label 'images/03-memory-pipeline.svg' in docs/services/microsoft-foundry/agent-memory/index.md, + pointing to docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/03-memory-pipeline.svg. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: tables + fingerprint: 8e1328630f60147e6c4f3bb21c1f846ca73a4c7ec8c2d3beffd0918db9a5c670 + count: 1 + - kind: file + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/03-memory-pipeline.svg + sha256: 1588cb6240cbeba2f41b9725783b48fb8f5a1b87f22f1cfd6bc2e4e54631fbb1 + 9e5f8f51ef96fa84bc4552892a69cdfc89d8f94a5ef1a822ceabeddf02e35464: + missing_count: 1 + reason: Retained label 'images/04-production-tiers.svg' in docs/services/microsoft-foundry/agent-memory/index.md, + pointing to docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/04-production-tiers.svg. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: tables + fingerprint: 9c91bd9ca1db92d41d335562d68a03bcec5cdd113c668ac45d465d55ce4cd706 + count: 1 + - kind: file + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/04-production-tiers.svg + sha256: f1e42cc6e3987923e8c849f3228edad7d5cce8b7ccd80d5e091b39f1e3d3aafb + fe2b3e96ca22941642e539d494b3861481ebfc8abc5a54ec0a1b5705f21b25b9: + missing_count: 1 + reason: Retained label 'images/05-commerce-blueprint.svg' in docs/services/microsoft-foundry/agent-memory/index.md, + pointing to docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/05-commerce-blueprint.svg. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: tables + fingerprint: 0aa6f09bfeab29d6ef473471f26333a1ffa3a34c1bbcfb10fcb4d6e7cf322a2c + count: 1 + - kind: file + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/05-commerce-blueprint.svg + sha256: ad6cda6b2f207d1ac7d5f2ee19bb8e53ee5d292942c29967d14b271a0b372ab6 + e545fbfd8a6db9a606f96c20118846de2fb6ce95bd45a6a6c9707adb282da640: + missing_count: 1 + reason: Restored label '06. 커머스 적용 설계' in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + section "문서 구성", linking to docs/services/microsoft-foundry/agent-memory/commerce/index.md. + The original detailed audience map remains explicitly historical and is linked from the canonical + guide. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: local_link_labels + fingerprint: e545fbfd8a6db9a606f96c20118846de2fb6ce95bd45a6a6c9707adb282da640 + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: tables + fingerprint: b161a4f98ec2067c6e78b388a7631bb35b87ca64254f845d5246223793bd7257 + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: prose + fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 + count: 1 + a63a178680757909d7b1cb6ad4e67e89420ea1882685f828f240218517493cb2: + missing_count: 1 + reason: Restored label '03. 파이프라인과 검색' in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + section "문서 구성", linking to docs/services/microsoft-foundry/agent-memory/pipeline-retrieval/index.md. + The original detailed audience map remains explicitly historical and is linked from the canonical + guide. + evidence: + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: local_link_labels + fingerprint: a63a178680757909d7b1cb6ad4e67e89420ea1882685f828f240218517493cb2 + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md + category: tables + fingerprint: 2013c3ede52cd3f5bbc066a7c88e6c3ae08e0ab49008c0a7a0c39f95085f7708 + count: 1 + - kind: structure + path: docs/services/microsoft-foundry/agent-memory/index.md + category: prose + fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 + count: 1 - baseline_path: memory/foundry-gpt-memory-v1.md pages_path: guides/microsoft-foundry/gpt-memory-layer/index.md reviewed_changes: prose: - ec8de6ca2bb54061c8ece964f0c28859efdb393ce95b035616d56bb021e61ae3: 'In docs/services/microsoft-foundry/gpt-memory-layer/index.md: - Safety: the opening verification-complete assertion and dated closing assertion were replaced with - an explicit semantic-verification-needed notice. The historical 2026-07-22 update date and architecture - content remain; the current front matter is needs-review, so the old unsupported verification claim - must not be restored. Current prose replacement begins: ''이관된 원문입니다. 공식 Microsoft Learn 문서와의 의미 - 검증이 필요합니다.''.' - 78dd2dad9b02afaa7f3308466ce27391076bda54ee2f32dd0e0c28e911c5a0f1: 'In docs/services/microsoft-foundry/gpt-memory-layer/index.md: - Safety: the opening verification-complete assertion and dated closing assertion were replaced with - an explicit semantic-verification-needed notice. The historical 2026-07-22 update date and architecture - content remain; the current front matter is needs-review, so the old unsupported verification claim - must not be restored. Current prose replacement begins: ''마지막 업데이트: 2026년 7월 22일 — 공식 원문 의미 검증 대기''.' + ec8de6ca2bb54061c8ece964f0c28859efdb393ce95b035616d56bb021e61ae3: + missing_count: 1 + reason: 'In docs/services/microsoft-foundry/gpt-memory-layer/index.md: Safety: the opening verification-complete + assertion and dated closing assertion were replaced with an explicit semantic-verification-needed + notice. The historical 2026-07-22 update date and architecture content remain; the current + front matter is needs-review, so the old unsupported verification claim must not be restored. + Current prose replacement begins: ''이관된 원문입니다. 공식 Microsoft Learn 문서와의 의미 검증이 필요합니다.''.' + evidence: + - kind: structure + path: docs/services/microsoft-foundry/gpt-memory-layer/index.md + category: prose + fingerprint: 52fe1e2fda546c026381b09925e9e24639fc02328832674336e59f13b20dc033 + count: 1 + 78dd2dad9b02afaa7f3308466ce27391076bda54ee2f32dd0e0c28e911c5a0f1: + missing_count: 1 + reason: 'In docs/services/microsoft-foundry/gpt-memory-layer/index.md: Safety: the opening verification-complete + assertion and dated closing assertion were replaced with an explicit semantic-verification-needed + notice. The historical 2026-07-22 update date and architecture content remain; the current + front matter is needs-review, so the old unsupported verification claim must not be restored. + Current prose replacement begins: ''마지막 업데이트: 2026년 7월 22일 — 공식 원문 의미 검증 대기''.' + evidence: + - kind: structure + path: docs/services/microsoft-foundry/gpt-memory-layer/index.md + category: prose + fingerprint: 9a723f48138487e2c68e1428681f27ab5875c5a431fa75f64f5209d20e17c156 + count: 1 - baseline_path: monitor/azure-monitor-dynamic-thresholds-brief.md pages_path: research/azure-monitor/dynamic-thresholds-brief/index.md reviewed_changes: {} @@ -740,639 +1926,1626 @@ documents: pages_path: guides/azure-monitor/azure-sre-agent-overview/index.md reviewed_changes: local_link_labels: - c1ca913091c95d35a8482f0216524d86a34dd4070a62f9ca3ec7a89bef6abd81: Retained label '편집 가능한 SVG 보기' in - docs/services/azure-monitor/azure-sre-agent/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/sre-agent-process.svg; - docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/s1-three-panel.svg. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 6acfc255c5e8f3e2260323007f2f377c059f04a4cf27090d45f826a526d4d4f7: Retained label 'Issue 본문 보기' in - docs/services/azure-monitor/azure-sre-agent/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-github-issue.md. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 875bedfdbc15c549d09155e977dc244e9e99297dbf14c1ac75dee7f9ec774840: Retained label 'HTML 메일 보기' in docs/services/azure-monitor/azure-sre-agent/index.md, - pointing to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-incident-summary.html. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 6d2ba3851d9f7930b9b842b58ab1c5a5124455736915e1a215b0f3aa3b10101d: Retained label 'RFC 5322 메일 파일 보기' - in docs/services/azure-monitor/azure-sre-agent/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-incident-summary.eml. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. + c1ca913091c95d35a8482f0216524d86a34dd4070a62f9ca3ec7a89bef6abd81: + missing_count: 2 + reason: Retained label '편집 가능한 SVG 보기' in docs/services/azure-monitor/azure-sre-agent/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/sre-agent-process.svg; + docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/s1-three-panel.svg. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/index.md + category: prose + fingerprint: 6c43c121382840f8f19937ac23214b0f78528fd1586cf8320fd202c62f28fd04 + count: 2 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/sre-agent-process.svg + sha256: 51f68bd5daecfafaa47702f98c6753775fee7f2deb4e772f65b6d86f161f0095 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/s1-three-panel.svg + sha256: f8ab268f0999ddb6727b62ab999c05f6686d73f9f48d7db471cf9a18ea2a95f3 + 6acfc255c5e8f3e2260323007f2f377c059f04a4cf27090d45f826a526d4d4f7: + missing_count: 1 + reason: Retained label 'Issue 본문 보기' in docs/services/azure-monitor/azure-sre-agent/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-github-issue.md. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/index.md + category: prose + fingerprint: 53ddda52b094ee04b6f81e7437bc88e8c6882150fe44286728e3688c7c91e39a + count: 1 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-github-issue.md + sha256: 6f25ab7c79a3950ccfbb0ca34a8f69e6e10855c350f940ef6e1286b05b143f1e + 875bedfdbc15c549d09155e977dc244e9e99297dbf14c1ac75dee7f9ec774840: + missing_count: 1 + reason: Retained label 'HTML 메일 보기' in docs/services/azure-monitor/azure-sre-agent/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-incident-summary.html. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/index.md + category: prose + fingerprint: 3d9b22355c89174f45308e79e3ac1eaa707a16e1fefe632338dbc844c7fe01d6 + count: 1 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-incident-summary.html + sha256: c025993ddafaacaaa12bc956e20a036ac1046ecd052a3de4aa5ab72549f66b90 + 6d2ba3851d9f7930b9b842b58ab1c5a5124455736915e1a215b0f3aa3b10101d: + missing_count: 1 + reason: Retained label 'RFC 5322 메일 파일 보기' in docs/services/azure-monitor/azure-sre-agent/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-incident-summary.eml. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/index.md + category: prose + fingerprint: 2223d51a898ea9e198ef287b34459e809b143c354f0a9ee36656d234e5190597 + count: 1 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-incident-summary.eml + sha256: 625a4cfad60d508c243ef361bc9bafb30391651c23dd9478ec0de98f34d2a2f6 - baseline_path: monitor/hdinsight-kafka-monitoring.md pages_path: research/azure-monitor/hdinsight-kafka-monitoring-options/index.md reviewed_changes: prose: - 571ffe34f1b3d57af92b1ccf0de6e92a2589f0e24650bde126c8265d737dec0e: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md: - The three installation/dashboard references now name Prometheus + Grafana 구성 가이드 at docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md. - Safety: the closing measured-environment note uses rg-example-japaneast-01 and hdi-example-japaneast-01, - retaining the scoped historical observation rather than making a new general product claim. Current - prose replacement begins: ''- 설치·대시보드 구성: Prometheus + Grafana 구성 가이드''.' - 8867b2b135aa92de4daaf8b97edbfbca3de5f620a9d4afb56b1e01a8caade5e0: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md: - The three installation/dashboard references now name Prometheus + Grafana 구성 가이드 at docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md. - Safety: the closing measured-environment note uses rg-example-japaneast-01 and hdi-example-japaneast-01, - retaining the scoped historical observation rather than making a new general product claim. Current - prose replacement begins: ''HDInsight 기본 미제공 → 수동 설치. 둘 다 Prometheus 스크래핑 + Grafana 시각화 조합. 설치·대시보드: - Prometheus + Grafana 구성 가이드''.' - 7843f6e01e3ff68bb42fbe436f2c615c652654b170f65926fe856e0cd6e2779b: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md: - The three installation/dashboard references now name Prometheus + Grafana 구성 가이드 at docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md. - Safety: the closing measured-environment note uses rg-example-japaneast-01 and hdi-example-japaneast-01, - retaining the scoped historical observation rather than making a new general product claim. Current - prose replacement begins: ''권장 목표 상태: Monitor Integration으로 노드/클러스터 baseline 확보 + 브로커/토픽 상세·JVM - internals·Consumer Lag은 JMX/Kafka Exporter로 관측. → 설치·대시보드: Prometheus + ''.' - 4f6d715673a43b9318d459439569bad301fbf80e4a9736a4a160e68e4dfa2982: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md: - The three installation/dashboard references now name Prometheus + Grafana 구성 가이드 at docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md. - Safety: the closing measured-environment note uses rg-example-japaneast-01 and hdi-example-japaneast-01, - retaining the scoped historical observation rather than making a new general product claim. Current - prose replacement begins: ''이 문서는 rg-example-japaneast-01 배포(hdi-example-japaneast-01) 기준 실측 기록. - 리소스명·IP는 환경에 맞게 치환.''.' + 571ffe34f1b3d57af92b1ccf0de6e92a2589f0e24650bde126c8265d737dec0e: + missing_count: 1 + reason: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md: The three installation/dashboard + references now name Prometheus + Grafana 구성 가이드 at docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md. + Safety: the closing measured-environment note uses rg-example-japaneast-01 and hdi-example-japaneast-01, + retaining the scoped historical observation rather than making a new general product claim. + Current prose replacement begins: ''- 설치·대시보드 구성: Prometheus + Grafana 구성 가이드''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md + category: prose + fingerprint: f219640494edde49577f87dd0634e045144c16f506a4a35d479c7755068a3f87 + count: 1 + 8867b2b135aa92de4daaf8b97edbfbca3de5f620a9d4afb56b1e01a8caade5e0: + missing_count: 1 + reason: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md: The three installation/dashboard + references now name Prometheus + Grafana 구성 가이드 at docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md. + Safety: the closing measured-environment note uses rg-example-japaneast-01 and hdi-example-japaneast-01, + retaining the scoped historical observation rather than making a new general product claim. + Current prose replacement begins: ''HDInsight 기본 미제공 → 수동 설치. 둘 다 Prometheus 스크래핑 + Grafana + 시각화 조합. 설치·대시보드: Prometheus + Grafana 구성 가이드''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md + category: prose + fingerprint: 6b0c69440e3985638be2b8a7660238ff8461cafabceb1d2d71ca8ba5bab0d8d0 + count: 1 + 7843f6e01e3ff68bb42fbe436f2c615c652654b170f65926fe856e0cd6e2779b: + missing_count: 1 + reason: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md: The three installation/dashboard + references now name Prometheus + Grafana 구성 가이드 at docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md. + Safety: the closing measured-environment note uses rg-example-japaneast-01 and hdi-example-japaneast-01, + retaining the scoped historical observation rather than making a new general product claim. + Current prose replacement begins: ''권장 목표 상태: Monitor Integration으로 노드/클러스터 baseline 확보 + + 브로커/토픽 상세·JVM internals·Consumer Lag은 JMX/Kafka Exporter로 관측. → 설치·대시보드: Prometheus + ''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md + category: prose + fingerprint: bf148487446161682e0bb6c28084af2b4035ba84be7f83ff237fcfa85f695cf2 + count: 1 + 4f6d715673a43b9318d459439569bad301fbf80e4a9736a4a160e68e4dfa2982: + missing_count: 1 + reason: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md: The three installation/dashboard + references now name Prometheus + Grafana 구성 가이드 at docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md. + Safety: the closing measured-environment note uses rg-example-japaneast-01 and hdi-example-japaneast-01, + retaining the scoped historical observation rather than making a new general product claim. + Current prose replacement begins: ''이 문서는 rg-example-japaneast-01 배포(hdi-example-japaneast-01) + 기준 실측 기록. 리소스명·IP는 환경에 맞게 치환.''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md + category: prose + fingerprint: 83ecd5eaa15e1a5d922441e31a87dd7dc47e5c341bc6a8072002ebe204e3981d + count: 1 local_link_labels: - 79baf84e4c913a8549739c6081e3a9cf1b0d9c1acc04ba9480129bd96358e6d9: Replaced retired path label with - 'Prometheus + Grafana 구성 가이드' in docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md, - pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. + 79baf84e4c913a8549739c6081e3a9cf1b0d9c1acc04ba9480129bd96358e6d9: + missing_count: 3 + reason: Replaced retired path label with 'Prometheus + Grafana 구성 가이드' in docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md, + pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md + category: local_link_labels + fingerprint: 694ca5df68c8496b993b6657861dd36d3d0d3d95abab7d5cf23ff2f0f587598e + count: 3 + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md + category: title + fingerprint: 8a3b50c0fb8014d577ca4609aeb5e8d1d7b0c2b6b3892893a5124296ca8f5b5b + count: 1 - baseline_path: monitor/hdinsight-kafka-prometheus-grafana.md pages_path: guides/azure-monitor/hdinsight-kafka-prometheus-grafana/index.md reviewed_changes: prose: - bd7b9c198dc08d1410385e42f06657cf6be04bff2f73972e56064ad96f5b3b64: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md: - The Why reference now names the canonical monitoring overview. The dashboard image instructions - distinguish bundle images from original sources at docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images. - Exporter rules, Prometheus configuration and Grafana dashboard source files remain in that owning - sample. Current prose replacement begins: ''전제: HDInsight Kafka 클러스터 + Prometheus + Grafana 기구성. - 도구별 커버 범위·선택 근거("Why") → HDInsight Kafka 모니터링 방식 비교.''.' - ed4fb9a1ee16fecca8e6bcc2f346062915290e65a9f2f82f9512af21bcea8a24: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md: - The Why reference now names the canonical monitoring overview. The dashboard image instructions - distinguish bundle images from original sources at docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images. - Exporter rules, Prometheus configuration and Grafana dashboard source files remain in that owning - sample. Current prose replacement begins: ''렌더링 이미지는 이 문서 번들의 images/ 디렉터리에 아래 파일명으로 두면 자동 표시됩니다. - 원본 자료는 samples/monitoring-assets/images/에서 확인할 수 있습니다.''.' + bd7b9c198dc08d1410385e42f06657cf6be04bff2f73972e56064ad96f5b3b64: + missing_count: 1 + reason: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md: + The Why reference now names the canonical monitoring overview. The dashboard image instructions + distinguish bundle images from original sources at docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images. + Exporter rules, Prometheus configuration and Grafana dashboard source files remain in that + owning sample. Current prose replacement begins: ''전제: HDInsight Kafka 클러스터 + Prometheus + + Grafana 기구성. 도구별 커버 범위·선택 근거("Why") → HDInsight Kafka 모니터링 방식 비교.''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md + category: prose + fingerprint: 4e42c4c7755af764dbe9ce95dd416c3f5648a697fcb6d133a72714b41984aa5c + count: 1 + ed4fb9a1ee16fecca8e6bcc2f346062915290e65a9f2f82f9512af21bcea8a24: + missing_count: 1 + reason: 'In docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md: + The Why reference now names the canonical monitoring overview. The dashboard image instructions + distinguish bundle images from original sources at docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images. + Exporter rules, Prometheus configuration and Grafana dashboard source files remain in that + owning sample. Current prose replacement begins: ''렌더링 이미지는 이 문서 번들의 images/ 디렉터리에 아래 파일명으로 + 두면 자동 표시됩니다. 원본 자료는 samples/monitoring-assets/images/에서 확인할 수 있습니다.''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md + category: prose + fingerprint: 4170b203da21be0358e01189a49fd4f678801580607a335931505606d0072672 + count: 1 local_link_labels: - 9e702f459d67c67952e1ebd636ab6a69f895283b704f440bab1613f3f09c09a4: Replaced retired path label with - 'HDInsight Kafka 모니터링 방식 비교' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, - pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md. The current canonical - source or owning-sample target was checked; this is a source-link/reader-label migration, not removal - of the referenced content. - 4cbc12caed5b287e12ad58a40e0bffb7faca665597ab4ee1c45211467bb82ca2: Retained label 'assets/prometheus-grafana/' - in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, pointing - to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 8caa2a204eec50db21c591280d846fac7d80f202c6a829dd8b3843700d5f969f: Retained label 'jmx/kafka-rules.yml' - in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, pointing - to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/jmx/kafka-rules.yml. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 656c7a584417e11b56b29c13f30fb5ca9620bb81643a4e4ba550f196afd4e658: Retained label 'jmx/b0.yml.example' - in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, pointing - to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/jmx/b0.yml.example. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - cc9c87015e90fe008b18f1d9853e54ba7d7b1eb21c6a9971039128ade73a1891: Retained label 'docker-compose.yml' - in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, pointing - to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/docker-compose.yml. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 50cb58547b9a5b500c46f975848fcb4e22aef66b2c517dc81ba944f5794afc09: Retained label 'prometheus.yml' - in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, pointing - to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/prometheus.yml. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - d8b0873d32bd4f22164ceaffdf91a9df0788a4195931f31b3c8c4f8aa9180b86: Retained label 'grafana/provisioning/datasources/prometheus.yml' - in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, pointing - to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/provisioning/datasources/prometheus.yml. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 2f9d7fc6e302cf2d64c80a3af3ad49d6363a4073da7278794d0212cdc69c2171: Retained label 'grafana/provisioning/dashboards/provider.yml' - in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, pointing - to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/provisioning/dashboards/provider.yml. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 4e30c0a736648cdbf3724c22290d2ce4d6e6467ffb8ec18015360f91aa22c5b2: Retained label 'kafka-exporter-adv.json' - in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, pointing - to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/dashboards/kafka-exporter-adv.json. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - e27b1fb145ebd9254aa8762cf881afc092d7a1388639715e48de4b6b0a492615: Retained label 'kafka-jmx-broker.json' - in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, pointing - to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/dashboards/kafka-jmx-broker.json. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 9e77ace5a4ee3cf33c393827cfda9dc6a2dfa57024e91cfda8a73e5919c8c10c: Replaced retired path label with - 'samples/monitoring-assets/images/' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, - pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. + 9e702f459d67c67952e1ebd636ab6a69f895283b704f440bab1613f3f09c09a4: + missing_count: 1 + reason: Replaced retired path label with 'HDInsight Kafka 모니터링 방식 비교' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, + pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md. The current canonical + source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md + category: local_link_labels + fingerprint: a4bddffc5fe27ac29dee1db3247764f0bcd4add39b2363df11e3c32386124149 + count: 1 + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md + category: title + fingerprint: 6b2f2e2b757771a85135aab52ed8f6f23cb7c1f61c01a4792c1b57fd5edd6ad0 + count: 1 + 4cbc12caed5b287e12ad58a40e0bffb7faca665597ab4ee1c45211467bb82ca2: + missing_count: 1 + reason: Retained label 'assets/prometheus-grafana/' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, + pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md + category: prose + fingerprint: 41a658b52dbbc527239649d30f140290f06454456f95dd05ba03666bcd38474a + count: 1 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/docker-compose.yml + sha256: e6486b8cd25c41b7a5b38004e2d9a680c9599919158857499d61a215310dfeb3 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/dashboards/kafka-exporter-adv.json + sha256: 15bda4477eb873e7e333b75265e75c9985c54bbb03324efe8c4ecc08f4f01d92 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/dashboards/kafka-jmx-broker.json + sha256: 2863a6a527be917245bf1af98e0bdf3bd4620ded6a58ef7a3dba24279406ef0d + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/provisioning/dashboards/provider.yml + sha256: 85c52f125f444030ffacf8f2be3854dc6c69e50f9e35327720aa2332e3071d0e + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/provisioning/datasources/prometheus.yml + sha256: 19d98cca02290926efd3e8b622d97213f3c19ed9b561898225d8e149ff48d9d6 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/jmx/b0.yml.example + sha256: 8d9a12fa3dcbc0661b1512125d8fcb63fe16d27e1839edc723f886e8f8f6ba53 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/jmx/kafka-rules.yml + sha256: 5e080b0afde650f3c8a619f5da3c1045cfa427bb6250125560158fa8f170370b + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/prometheus.yml + sha256: b32f4f5111347a0f0f897ecb795cdcff69ccb563ceee3080f90a71aeec52bebd + 8caa2a204eec50db21c591280d846fac7d80f202c6a829dd8b3843700d5f969f: + missing_count: 1 + reason: Retained label 'jmx/kafka-rules.yml' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, + pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/jmx/kafka-rules.yml. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md + category: prose + fingerprint: d2572892b8f76473f2b077c80e99342fc69d2e420aa2733e2fa1a25e3bb4243b + count: 1 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/jmx/kafka-rules.yml + sha256: 5e080b0afde650f3c8a619f5da3c1045cfa427bb6250125560158fa8f170370b + 656c7a584417e11b56b29c13f30fb5ca9620bb81643a4e4ba550f196afd4e658: + missing_count: 1 + reason: Retained label 'jmx/b0.yml.example' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, + pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/jmx/b0.yml.example. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md + category: prose + fingerprint: 099a3460e79790844e71afef0f2fc65bb0aa8f3ab3ee0d36facd51677101b570 + count: 1 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/jmx/b0.yml.example + sha256: 8d9a12fa3dcbc0661b1512125d8fcb63fe16d27e1839edc723f886e8f8f6ba53 + cc9c87015e90fe008b18f1d9853e54ba7d7b1eb21c6a9971039128ade73a1891: + missing_count: 1 + reason: Retained label 'docker-compose.yml' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, + pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/docker-compose.yml. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md + category: prose + fingerprint: 53a7f8bb6c355c3a1cfbc1133f81d6c439ddd1e3336cadb7b24ab21665ed2a98 + count: 1 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/docker-compose.yml + sha256: e6486b8cd25c41b7a5b38004e2d9a680c9599919158857499d61a215310dfeb3 + 50cb58547b9a5b500c46f975848fcb4e22aef66b2c517dc81ba944f5794afc09: + missing_count: 1 + reason: Retained label 'prometheus.yml' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, + pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/prometheus.yml. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md + category: prose + fingerprint: ddc1de35507a6b889a07c31a711d342621cd2e082d84a1b52d8a9751403e996d + count: 1 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/prometheus.yml + sha256: b32f4f5111347a0f0f897ecb795cdcff69ccb563ceee3080f90a71aeec52bebd + d8b0873d32bd4f22164ceaffdf91a9df0788a4195931f31b3c8c4f8aa9180b86: + missing_count: 1 + reason: Retained label 'grafana/provisioning/datasources/prometheus.yml' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, + pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/provisioning/datasources/prometheus.yml. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md + category: prose + fingerprint: 9cc2785f78c81f84cb145c7e6bec527f36fdf0ed93babec1d6cdeda5fc61c79c + count: 1 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/provisioning/datasources/prometheus.yml + sha256: 19d98cca02290926efd3e8b622d97213f3c19ed9b561898225d8e149ff48d9d6 + 2f9d7fc6e302cf2d64c80a3af3ad49d6363a4073da7278794d0212cdc69c2171: + missing_count: 1 + reason: Retained label 'grafana/provisioning/dashboards/provider.yml' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, + pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/provisioning/dashboards/provider.yml. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md + category: prose + fingerprint: 01913cdfe45180eae604605a28602a0500a8bd870582c179be8a7b1334a20996 + count: 1 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/provisioning/dashboards/provider.yml + sha256: 85c52f125f444030ffacf8f2be3854dc6c69e50f9e35327720aa2332e3071d0e + 4e30c0a736648cdbf3724c22290d2ce4d6e6467ffb8ec18015360f91aa22c5b2: + missing_count: 1 + reason: Retained label 'kafka-exporter-adv.json' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, + pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/dashboards/kafka-exporter-adv.json. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md + category: tables + fingerprint: 2c2b7f6f91a5377e7a17c17d6310fb291834cf0a7337617ae8ec23a5b21d88e7 + count: 1 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/dashboards/kafka-exporter-adv.json + sha256: 15bda4477eb873e7e333b75265e75c9985c54bbb03324efe8c4ecc08f4f01d92 + e27b1fb145ebd9254aa8762cf881afc092d7a1388639715e48de4b6b0a492615: + missing_count: 1 + reason: Retained label 'kafka-jmx-broker.json' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, + pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/dashboards/kafka-jmx-broker.json. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md + category: tables + fingerprint: 6173b636503367799735c56b567948813f96c3a8a71793a0ceaeb215de9e6722 + count: 1 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/dashboards/kafka-jmx-broker.json + sha256: 2863a6a527be917245bf1af98e0bdf3bd4620ded6a58ef7a3dba24279406ef0d + 9e77ace5a4ee3cf33c393827cfda9dc6a2dfa57024e91cfda8a73e5919c8c10c: + missing_count: 1 + reason: Replaced retired path label with 'samples/monitoring-assets/images/' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, + pointing to docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md + category: prose + fingerprint: 4170b203da21be0358e01189a49fd4f678801580607a335931505606d0072672 + count: 1 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images/hdinsight_monitor_integration_1.png + sha256: 2024f2b0f661865f11f3f7d8c6478a3e7dd7609d8529b8382d833705c8e3d065 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images/kafka-exporter-adv.png + sha256: 7ff379a7efb9fa79864deb94d405138a07aae3197dfefcc070a3ba041db5a209 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images/kafka-exporter-overview.png + sha256: d75968193a69d62cd577cc1d55af6e75c1139cf9621b56fe08b48014016d4533 + - kind: file + path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images/kafka-jmx-broker.png + sha256: aa4b75569d6ed9b26dbb4475e3f3729b56b6e79cbae21e37182040a5e2325240 - baseline_path: monitor/otel-monitor-setup.md pages_path: guides/azure-monitor/aks-private-opentelemetry/index.md reviewed_changes: prose: - c1a9ea48124d32711ff584f255753d47c4f12ffbe17ad6148c69e05e72afb1bd: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current prose replacement begins: - ''1. 이름: ampls-example-krc-01''.' - 5d1c0fcbfc31548179d5d0756ae3fb3974d997bf4df292a161ab641dd7c85e03: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current prose replacement begins: - ''- MSProm-koreacentral-aks-example-koreacentral (RG: rg-example-koreacentral-01)''.' - 8fbeff86074094523c052738ea0e6e18842511c0ea484c8d322d06c2f94f2feb: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current prose replacement begins: - ''- azuremonitor-workspace-example-krc-01 (RG: MA_azuremonitor-workspace-example-krc-01_koreacentral_managed)''.' - ad04f0f2877e6e144327ec3be3e5105db452638ca45e94fdd3adc3d96c3615be: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current prose replacement begins: - ''- 이름: pe-ampls-example-krc-01''.' - ee3ef39d4d43df6bf7c946a70c01af30c9f3ed941567964fc003decfa83d93a1: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current prose replacement begins: - ''1. Prometheus DCR 선택 (예: MSProm-koreacentral-aks-example-koreacentral-01)''.' - f998153bb254b703c5a1fb950d62e9d8315c1952cbb5d95e5a7f9846303212f4: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current prose replacement begins: - ''기존 VNet(vnet-example-koreacentral-01)에 연결되어 있던 모든 Private DNS Zone 링크를 PRD VNet으로 이전.''.' - d071edd197f1c6d03c88e8491ddb4280af4830a85448b1388e50296b6028bc31: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current prose replacement begins: - ''- Private Endpoint(pe-ampls-example-krc-01)는 기존 VNet의 pe-subnet에 그대로 위치''.' + c1a9ea48124d32711ff584f255753d47c4f12ffbe17ad6148c69e05e72afb1bd: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current prose replacement + begins: ''1. 이름: ampls-example-krc-01''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: prose + fingerprint: bbc574c89ca549a6062390468e8adfc409470634a1ff93ca6fad57ea22e3d910 + count: 1 + 5d1c0fcbfc31548179d5d0756ae3fb3974d997bf4df292a161ab641dd7c85e03: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current prose replacement + begins: ''- MSProm-koreacentral-aks-example-koreacentral (RG: rg-example-koreacentral-01)''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: prose + fingerprint: c1f2483ebd9388bbaf330b59e957cb66a504b955e3339b66d3a44de3b823cced + count: 1 + 8fbeff86074094523c052738ea0e6e18842511c0ea484c8d322d06c2f94f2feb: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current prose replacement + begins: ''- azuremonitor-workspace-example-krc-01 (RG: MA_azuremonitor-workspace-example-krc-01_koreacentral_managed)''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: prose + fingerprint: c0451df8ad1cfd13db5ccb6fb5934f94b6f0ca7f2db132b69da63df74542e457 + count: 1 + ad04f0f2877e6e144327ec3be3e5105db452638ca45e94fdd3adc3d96c3615be: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current prose replacement + begins: ''- 이름: pe-ampls-example-krc-01''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: prose + fingerprint: 0fb25eca1ab807fbf74b0e385aa1d17fbc5a7fb3d633a84cf1de9bd88ff602c0 + count: 1 + ee3ef39d4d43df6bf7c946a70c01af30c9f3ed941567964fc003decfa83d93a1: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current prose replacement + begins: ''1. Prometheus DCR 선택 (예: MSProm-koreacentral-aks-example-koreacentral-01)''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: prose + fingerprint: b155cc6748cd008ba20cbd36d902c1bddd67ac433c79b4d6096f66fc7e08e64a + count: 1 + f998153bb254b703c5a1fb950d62e9d8315c1952cbb5d95e5a7f9846303212f4: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current prose replacement + begins: ''기존 VNet(vnet-example-koreacentral-01)에 연결되어 있던 모든 Private DNS Zone 링크를 PRD VNet으로 + 이전.''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: prose + fingerprint: 171f3f27014b606e8fe143a42696a97b44f7597e61e63dee231da1c17970805c + count: 1 + d071edd197f1c6d03c88e8491ddb4280af4830a85448b1388e50296b6028bc31: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current prose replacement + begins: ''- Private Endpoint(pe-ampls-example-krc-01)는 기존 VNet의 pe-subnet에 그대로 위치''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: prose + fingerprint: 808b1302a9e4eeae104b8a710f78ea83a68fac2c0155c755f489722efc1909d0 + count: 1 code: - 737daa04ea7fec43ce993922f36de08c0acfb5c59b90f06c95a6abd74b2fe37a: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current code replacement begins: - ''flowchart TB subgraph AKS["AKS Private Cluster"] subgraph APP_NS["Application Namespace"] APP["Application - Pod
(OTLP SDK)"] end subgraph''.' - 4102e9632a8cf0e48ab0caeb6f23a0402430bbb15093989dc8154244e0158a3a: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current code replacement begins: - ''# AKS 파드 내부에서 nslookup msprom-koreacentral-aks-example-koreacentral-ruja.koreacentral-1.metrics.ingest.monitor.azure.com - # → 10.2.0.6 (Priva''.' - 1e19db832dd173940587309e96ea10d33c9bab0961486660f2a096ec2a9025b4: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current code replacement begins: - ''az rest --method put \\ --url "/subscriptions//resourceGroups/rg-example-koreacentral-01/providers/Microsoft.ContainerServic''.' - 1429b56f03ae454fc3a1269d43d36e7ec78701d174e706a4b5990006273e61e0: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current code replacement begins: - ''az rest --method get \\ --url "/subscriptions//resourceGroups/rg-example-koreacentral-01/providers/Microsoft.ContainerServic''.' - c5ad99e6336f4dc7dfb1f317452800240fc429c588e2bb3f5e6b90d3e22e8b9b: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current code replacement begins: - ''flowchart TB VM["VM / AKS Pod
(vnet-example-koreacentral-01)"] -->|"DNS Query"| RESOLVER["systemd-resolved
127.0.0.53"] - RESOLVER -->''.' - 4cc41bf5a9cd2b01eaefbd95bd337c47e7dbec1543ff325a21fdb7ab4eda69b3: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current code replacement begins: - ''# PRD VNet에 링크 추가 az network private-dns link vnet create \\ --name "link-to-prd-vnet" \\ --zone-name - "" \\ --resource-group ". Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current code replacement + begins: ''flowchart TB subgraph AKS["AKS Private Cluster"] subgraph APP_NS["Application Namespace"] + APP["Application Pod
(OTLP SDK)"] end subgraph''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: code + fingerprint: 4ef1082f7cf220128cc308de31a66af20ff5da00c2409d7f9f039ae91b3590f3 + count: 1 + 4102e9632a8cf0e48ab0caeb6f23a0402430bbb15093989dc8154244e0158a3a: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current code replacement + begins: ''# AKS 파드 내부에서 nslookup msprom-koreacentral-aks-example-koreacentral-ruja.koreacentral-1.metrics.ingest.monitor.azure.com + # → 10.2.0.6 (Priva''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: code + fingerprint: a400fadc841f3d5ed9bafe714adab7fdb56b965987d3f22bf647120d8cbc6bbb + count: 1 + 1e19db832dd173940587309e96ea10d33c9bab0961486660f2a096ec2a9025b4: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current code replacement + begins: ''az rest --method put \\ --url "/subscriptions//resourceGroups/rg-example-koreacentral-01/providers/Microsoft.ContainerServic''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: code + fingerprint: 855602bc880e0789d36297b99eb04a465691cff9b72e5131b9f575164fcae21e + count: 1 + 1429b56f03ae454fc3a1269d43d36e7ec78701d174e706a4b5990006273e61e0: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current code replacement + begins: ''az rest --method get \\ --url "/subscriptions//resourceGroups/rg-example-koreacentral-01/providers/Microsoft.ContainerServic''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: code + fingerprint: 9ecf660a261d19a3d6b376a7104f0c354579fea4fe1a95c67129bc762889ebea + count: 1 + c5ad99e6336f4dc7dfb1f317452800240fc429c588e2bb3f5e6b90d3e22e8b9b: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current code replacement + begins: ''flowchart TB VM["VM / AKS Pod
(vnet-example-koreacentral-01)"] -->|"DNS Query"| + RESOLVER["systemd-resolved
127.0.0.53"] RESOLVER -->''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: code + fingerprint: 0f097ffb50dc9497206efc249fbb8b7de2275d1afe55d8364942dac2c4cfa607 + count: 1 + 4cc41bf5a9cd2b01eaefbd95bd337c47e7dbec1543ff325a21fdb7ab4eda69b3: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current code replacement + begins: ''# PRD VNet에 링크 추가 az network private-dns link vnet create \\ --name "link-to-prd-vnet" + \\ --zone-name "" \\ --resource-group ". Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''AKS 클러스터 | aks-example-koreacentral-01 (Private Cluster)''.' - bfd684c367e972b3460eb62a456f363c6bdbc785c002e59488aebda2284ba4a5: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''리소스 그룹 | rg-example-koreacentral-01''.' - 94c21b10dbe667b9f47b937912e7d995de72f5526d5e6c2802e25ad697f8dcce: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''PRD 리소스 그룹 | rg-example-koreacentral-prd-01''.' - 995158c790ba9fe3ee7f8a3232d66e14a647bffa46e7b8a5c6b7f20f1c746c72: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''Public IP | pip-fw-example-koreacentral-prd-01 | Standard SKU''.' - baacdbdcd3a0b86ce62dc3ecdaecb62c91b4f07f4f4db8154a961e75134e8286: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''VNet (기존) | vnet-example-koreacentral-01 (10.0.0.0/10)''.' - 60afb1c68779b99db7b65dff72dde0a4b3e66377f7579ac6e2a29b0d54a644ab: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''VNet (PRD) | vnet-example-koreacentral-prd-01 (10.100.0.0/16)''.' - ed16ab3e60e9fb812d6e89188b1346f14bc8c49a551d0665ff6312d60e05d336: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''Azure Firewall | fw-example-koreacentral-prd-01 (Private IP: 10.100.0.4)''.' - ddcefb2d91cc9d847a48657cdd22c75238df1e700e3957df9e7f7b0157e1ed7d: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''Azure Monitor Workspace | azuremonitor-workspace-example-krc-01''.' - 39224c38099b50425f28ad69fbe812a72684f6acb879bbbea236467626b5bc32: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''Managed Grafana | grafanaworkspaceexample''.' - d97e332b81cde3eba71a3802133c3cee677171ad8ad226385c783f33ff0b6f84: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''privatelink.monitor.azure.com | 메트릭 수집 엔드포인트 | vnet-example-koreacentral-prd-01''.' - b1bdcfb814366ed3a685131abb36a7a106f8723864d4d58a2e3d693cc5e69a4c: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''privatelink.ods.opinsights.azure.com | ODS 데이터 채널 | vnet-example-koreacentral-prd-01''.' - fbf53873149396c6970488dc818e37574b77531268e0ddf1c868f95f451ebd1d: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''privatelink.oms.opinsights.azure.com | OMS 포털 | vnet-example-koreacentral-prd-01''.' - a023b598ecac63bcd02648fef1a4c89bed7d2a4f2de51f4426dae8607ce0c2f1: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''privatelink.agentsvc.azure-automation.net | 에이전트 서비스 | vnet-example-koreacentral-prd-01''.' - 5ab80b1cde1ccc1cdc2d55d0034dda565fc8dcc1595d214c48cded114bbcb27a: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''privatelink.blob.core.windows.net | Blob 스토리지 | vnet-example-koreacentral-prd-01''.' - 4171198b18e12970a4d725b2ed7dfac43555ace5cc0aef041dd6dc9a725f9562: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''*.privatelink.koreacentral.azmk8s.io | AKS API Server | vnet-example-koreacentral-prd-01''.' - f59b5f0be1612339205a66b664559cf857a99375037daf859709b6efd7cd8d73: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''privatelink.redis.azure.net | Redis Cache | vnet-example-koreacentral-prd-01''.' - 933b1376898a2ead7b68157deb139af9a3f5101c60da5470896ae300bf07c27b: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''MSProm-koreacentral-aks-example-koreacentral-01 | rg-example-koreacentral-01 | MSProm-koreacentral-aks-example-koreacentral''.' - e545d145796957437bcbea7610d0e6f25f691fa2c07cbc63225a5d9e14122059: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''azuremonitor-workspace-example-krc-01 | MA_..._managed | azuremonitor-workspace-example-krc-01''.' - 6fcf8f81ea35d9515ed12cf8a5c242e7fa802cc887e79d2aacc43416ba0db048: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''configurationAccessEndpoint | N/A | MSProm-koreacentral-aks-example-koreacentral ✅''.' - 68b32234335d9a9ef7eadded9bfda2eb6768b72aa23e0a92979b92a546e65061: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''Resource Group | rg-example-koreacentral-prd-01 | Korea Central''.' - 791178e78ff10c8a4e214cbdbf680ece83bcfc93724ac7da171bf5ab1211ccfb: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''VNet | vnet-example-koreacentral-prd-01 | 10.100.0.0/16''.' - 06000515a008f2a2917bb02de0e4c0f686dbaeeefd02c3bbc5973fbc07c2987a: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''Azure Firewall | fw-example-koreacentral-prd-01 | Standard SKU, DNS Proxy 활성화''.' - e314e69e39f83a68b872625644fafa45e23c027f2feacc8c808f6c285a0b7798: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: - Safety: the environment, architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples - now use the example resource namespace and . Both az rest configurationAccessEndpoint - requests retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, - metrics pipeline, taints and historical topology details remain. Current tables replacement begins: - ''Public IP | pip-fw-example-koreacentral-prd-01 | Standard SKU''.' + 94c29301b4914f9b485a8db89906dd857a9196be32a5825331b49522a4b9e00c: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''AKS 클러스터 | aks-example-koreacentral-01 (Private Cluster)''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 2ca987a137329910594865b821e33e59f1f6bc0e60349d3dfabca7f89ada06a3 + count: 1 + bfd684c367e972b3460eb62a456f363c6bdbc785c002e59488aebda2284ba4a5: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''리소스 그룹 | rg-example-koreacentral-01''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 03128153a43f623a31a59f280f5cbb3c205d2056ee089aa5f7bf2072a8312bb6 + count: 1 + 94c21b10dbe667b9f47b937912e7d995de72f5526d5e6c2802e25ad697f8dcce: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''PRD 리소스 그룹 | rg-example-koreacentral-prd-01''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 41e982c6bcd601d258af47ac038aafe14fca72e4063766b169953c4374da97bd + count: 1 + 995158c790ba9fe3ee7f8a3232d66e14a647bffa46e7b8a5c6b7f20f1c746c72: + missing_count: 1 + reason: 'Safety: the historical subscription GUID is replaced by the exact row 구독 | + in docs/services/azure-monitor/aks-private-opentelemetry/index.md section "환경 개요". The approval + binds that subscription row, not the unrelated Public IP row; the surrounding topology is + retained.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: d09ce819b129bc823f5842be992fb00a2f5737a81342aecd189468103d722a28 + count: 1 + baacdbdcd3a0b86ce62dc3ecdaecb62c91b4f07f4f4db8154a961e75134e8286: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''VNet (기존) | vnet-example-koreacentral-01 (10.0.0.0/10)''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 481b907c8e8d5cf8e0b74ee4daae49deb7e69e0d968e197a2d9902be21feb385 + count: 1 + 60afb1c68779b99db7b65dff72dde0a4b3e66377f7579ac6e2a29b0d54a644ab: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''VNet (PRD) | vnet-example-koreacentral-prd-01 (10.100.0.0/16)''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: dabac1b4ff5bc5220e08b45697a9c4c2009cb6ca26c7682c6fffbead8615d18a + count: 1 + ed16ab3e60e9fb812d6e89188b1346f14bc8c49a551d0665ff6312d60e05d336: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''Azure Firewall | fw-example-koreacentral-prd-01 (Private IP: 10.100.0.4)''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 985dac84a65e64e9d1ba154b09f95b72ef2fb3215567e572f001f1c389225e46 + count: 1 + ddcefb2d91cc9d847a48657cdd22c75238df1e700e3957df9e7f7b0157e1ed7d: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''Azure Monitor Workspace | azuremonitor-workspace-example-krc-01''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 1887b1c0552d41df29a3c06b7d6107d7baca63687426cb48320fcf0d2e43b2b8 + count: 1 + 39224c38099b50425f28ad69fbe812a72684f6acb879bbbea236467626b5bc32: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''Managed Grafana | grafanaworkspaceexample''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: c06f1db22f748b70aa1bc356c385292d11b56dd52fdb84f5fa0e2a62b7d077e2 + count: 1 + d97e332b81cde3eba71a3802133c3cee677171ad8ad226385c783f33ff0b6f84: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''privatelink.monitor.azure.com | 메트릭 수집 엔드포인트 | vnet-example-koreacentral-prd-01''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 17bfcdfb17759f755da6134996411bed3843dbad727f581531c06147bf6d6bf2 + count: 1 + b1bdcfb814366ed3a685131abb36a7a106f8723864d4d58a2e3d693cc5e69a4c: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''privatelink.ods.opinsights.azure.com | ODS 데이터 채널 | vnet-example-koreacentral-prd-01''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 4bca0441b9eaad688d86cb48a00d560a5e4ae04d17e83f5d93219300ac10a58a + count: 1 + fbf53873149396c6970488dc818e37574b77531268e0ddf1c868f95f451ebd1d: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''privatelink.oms.opinsights.azure.com | OMS 포털 | vnet-example-koreacentral-prd-01''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 5fa00e6cd05ae23719f28e3a5214e55eda7ee5f2a52765aefc998266989dc380 + count: 1 + a023b598ecac63bcd02648fef1a4c89bed7d2a4f2de51f4426dae8607ce0c2f1: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''privatelink.agentsvc.azure-automation.net | 에이전트 서비스 | vnet-example-koreacentral-prd-01''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 349000b7e85a3d2b38888924d5de1718e1de1a07158130be4dfd9863aabc1388 + count: 1 + 5ab80b1cde1ccc1cdc2d55d0034dda565fc8dcc1595d214c48cded114bbcb27a: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''privatelink.blob.core.windows.net | Blob 스토리지 | vnet-example-koreacentral-prd-01''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 1da8e859896ce4aea549bc88d21118844eab3ab69b32cfbc410ff4d527b74281 + count: 1 + 4171198b18e12970a4d725b2ed7dfac43555ace5cc0aef041dd6dc9a725f9562: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''*.privatelink.koreacentral.azmk8s.io | AKS API Server | vnet-example-koreacentral-prd-01''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 10422feb79fbc47d77c3bc8268b12d7aae01e0ce6354c2507cf70c6f26ac6b5d + count: 1 + f59b5f0be1612339205a66b664559cf857a99375037daf859709b6efd7cd8d73: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''privatelink.redis.azure.net | Redis Cache | vnet-example-koreacentral-prd-01''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 5c19cc801bd492583b3ea899509ae32249f76ba8ffd376d2c530ddb1e514247d + count: 1 + 933b1376898a2ead7b68157deb139af9a3f5101c60da5470896ae300bf07c27b: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''MSProm-koreacentral-aks-example-koreacentral-01 | rg-example-koreacentral-01 | MSProm-koreacentral-aks-example-koreacentral''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 76d2c2f89804d133299f27b69c7dbb43c3b582eebc58be63852d9bbbcd4bbba6 + count: 1 + e545d145796957437bcbea7610d0e6f25f691fa2c07cbc63225a5d9e14122059: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''azuremonitor-workspace-example-krc-01 | MA_..._managed | azuremonitor-workspace-example-krc-01''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: d7dc8fb9516159fc1a347e8f5c770216913d2aa4fcb2fd382809f7ed182a49cb + count: 1 + 6fcf8f81ea35d9515ed12cf8a5c242e7fa802cc887e79d2aacc43416ba0db048: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''configurationAccessEndpoint | N/A | MSProm-koreacentral-aks-example-koreacentral + ✅''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 0bc918c9caee9e79a2db8af23a0554232018ffd121b175f3e7d4dfb69e00dde2 + count: 1 + 68b32234335d9a9ef7eadded9bfda2eb6768b72aa23e0a92979b92a546e65061: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''Resource Group | rg-example-koreacentral-prd-01 | Korea Central''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 14cbd2934a7c5ce85199362c79c6b8fc53a586b108d677530a0d36c847912f96 + count: 1 + 791178e78ff10c8a4e214cbdbf680ece83bcfc93724ac7da171bf5ab1211ccfb: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''VNet | vnet-example-koreacentral-prd-01 | 10.100.0.0/16''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 693e6a4d8337f736e6ff61a17d74a9c9df2d526a3e3ae457c1cbf7819fcdc8a9 + count: 1 + 06000515a008f2a2917bb02de0e4c0f686dbaeeefd02c3bbc5973fbc07c2987a: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''Azure Firewall | fw-example-koreacentral-prd-01 | Standard SKU, DNS Proxy 활성화''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: eac3ee1c7efff9f7a43924686095d9dd66de8c7c11c27bc8e827aa6fd03d7ddf + count: 1 + e314e69e39f83a68b872625644fafa45e23c027f2feacc8c808f6c285a0b7798: + missing_count: 1 + reason: 'In docs/services/azure-monitor/aks-private-opentelemetry/index.md: Safety: the environment, + architecture, AMPLS/DCE/DCR, DNS-zone, Firewall and VNet-link examples now use the example + resource namespace and . Both az rest configurationAccessEndpoint requests + retain their methods, 2023-03-11 API version and JSON properties. DNS zones, address ranges, + metrics pipeline, taints and historical topology details remain. Current tables replacement + begins: ''Public IP | pip-fw-example-koreacentral-prd-01 | Standard SKU''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/aks-private-opentelemetry/index.md + category: tables + fingerprint: 755dae66bd3362048c7611a80bdc4edf450e66edd00ddd0968f2426685157a69 + count: 1 - baseline_path: monitor/sre-agent-event-lab/README.md pages_path: labs/azure-monitor/sre-agent-event-lab/index.md reviewed_changes: prose: - 1c3a39700c2a8d219e620296cdc608dda909d0077f62895183cd2f5313544f14: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: - The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now - use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state - gates, cost warning, scoring and cleanup remain. Current prose replacement begins: ''포털에서만 할 수 있는 - 설정이 남아 있습니다. 저장소 연결, 지식 문서 업로드, Azure Monitor incident platform 연결, Review 모드 응답 계획, 역할 할당을 에이전트 - 설정이 순서대로 안내합니다.''.' - a2ad55106e8c2cb6f8391a41cadf665e3517675e3ff39d75ae5b57d1d1315db6: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: - The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now - use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state - gates, cost warning, scoring and cleanup remain. Current prose replacement begins: ''기본 실습에는 Logic - App bridge를 배포하지 않습니다. 제품 표준 경로는 Azure Monitor를 incident platform으로 연결하는 것이고, 예전 실측에서 쓰던 Action - Group + Logic App 인증 경로는 레거시 기''.' - 2ca86442294e2f99044c23fc5623c201686c9332f032e3d1253159630704d4e1: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: - The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now - use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state - gates, cost warning, scoring and cleanup remain. Current prose replacement begins: ''장애를 주입하기 전에 - 정상 부하가 Application Insights까지 도달하는지 확인하고, 그 사실을 기록해야 S1이 열립니다. 텔레메트리가 없는 워크로드에 장애를 넣으면 주입한 장애와 원래부터 - 안 보이던 상태를 구별할 수 없습니다. 명령은 에이''.' - f0a4ebba54d5beb20034578ede022134d9baa6047ad234c0223b2ccb6d014fa4: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: - The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now - use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state - gates, cost warning, scoring and cleanup remain. Current prose replacement begins: ''채점 기준, 사람이 - 채워야 하는 판정, 종합 판정 해석은 결과 채점에 있습니다.''.' - 33621ea5336f4b63bc4bffb679ec0aa3630e7dad73c975d29348530fdad07bf8: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: - The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now - use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state - gates, cost warning, scoring and cleanup remain. Current prose replacement begins: ''정적 임계값 대신 Dynamic - Threshold로 확장하는 설계는 Dynamic Thresholds 확장 설계에 정리해 두었습니다.''.' + 1c3a39700c2a8d219e620296cdc608dda909d0077f62895183cd2f5313544f14: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: The login/source + and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Setup, + baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, + state gates, cost warning, scoring and cleanup remain. Current prose replacement begins: ''포털에서만 + 할 수 있는 설정이 남아 있습니다. 저장소 연결, 지식 문서 업로드, Azure Monitor incident platform 연결, Review 모드 응답 계획, + 역할 할당을 에이전트 설정이 순서대로 안내합니다.''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: prose + fingerprint: 235a018f4b0bc18cc2d583417464286dedcded28ee0a0921ffdeca1c6db51f73 + count: 1 + a2ad55106e8c2cb6f8391a41cadf665e3517675e3ff39d75ae5b57d1d1315db6: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: The login/source + and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Setup, + baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, + state gates, cost warning, scoring and cleanup remain. Current prose replacement begins: ''기본 + 실습에는 Logic App bridge를 배포하지 않습니다. 제품 표준 경로는 Azure Monitor를 incident platform으로 연결하는 것이고, 예전 + 실측에서 쓰던 Action Group + Logic App 인증 경로는 레거시 기''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: prose + fingerprint: 486ae0fd422b20a9c48989eec7fea0295edec45a787e8cd1f00b54fa3a60ae53 + count: 1 + 2ca86442294e2f99044c23fc5623c201686c9332f032e3d1253159630704d4e1: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: The login/source + and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Setup, + baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, + state gates, cost warning, scoring and cleanup remain. Current prose replacement begins: ''장애를 + 주입하기 전에 정상 부하가 Application Insights까지 도달하는지 확인하고, 그 사실을 기록해야 S1이 열립니다. 텔레메트리가 없는 워크로드에 장애를 + 넣으면 주입한 장애와 원래부터 안 보이던 상태를 구별할 수 없습니다. 명령은 에이''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: prose + fingerprint: 140621f40b6e6829edba8ef8b5c986ded469ef2666d4f36b1f98fafe3ebc885c + count: 1 + f0a4ebba54d5beb20034578ede022134d9baa6047ad234c0223b2ccb6d014fa4: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: The login/source + and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Setup, + baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, + state gates, cost warning, scoring and cleanup remain. Current prose replacement begins: ''채점 + 기준, 사람이 채워야 하는 판정, 종합 판정 해석은 결과 채점에 있습니다.''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: prose + fingerprint: b9cd2d9d8b782900104f7a343e148055fd1519785df15e1c2cd00eeb6f34d372 + count: 1 + 33621ea5336f4b63bc4bffb679ec0aa3630e7dad73c975d29348530fdad07bf8: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: The login/source + and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Setup, + baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, + state gates, cost warning, scoring and cleanup remain. Current prose replacement begins: ''정적 + 임계값 대신 Dynamic Threshold로 확장하는 설계는 Dynamic Thresholds 확장 설계에 정리해 두었습니다.''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: prose + fingerprint: e488ae8fbc6bf58b7b5baf663fcf1d4cb0ff058fa3bcd547f69e0ad7af182896 + count: 1 code: - 6faff368410a9e4c46cfeda44df7b9d874c51daf2930ac47df1578133c4775b1: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: - The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now - use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state - gates, cost warning, scoring and cleanup remain. Current code replacement begins: ''az login --use-device-code - azd auth login source ./docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/lab-env.sh''.' - 7e2247eec243f924ffd1b30a2b412b3022389dfcbf3fba3b3cf5dc7d2f80e4c6: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: - The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now - use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state - gates, cost warning, scoring and cleanup remain. Current code replacement begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab''.' + 6faff368410a9e4c46cfeda44df7b9d874c51daf2930ac47df1578133c4775b1: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: The login/source + and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Setup, + baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, + state gates, cost warning, scoring and cleanup remain. Current code replacement begins: ''az + login --use-device-code azd auth login source ./docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/lab-env.sh''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: code + fingerprint: c4f3722e70e4fabe25384b17b0626591e6eb567ee527873ba09825e437d7dde3 + count: 1 + 7e2247eec243f924ffd1b30a2b412b3022389dfcbf3fba3b3cf5dc7d2f80e4c6: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: The login/source + and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Setup, + baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, + state gates, cost warning, scoring and cleanup remain. Current code replacement begins: ''cd + docs/services/azure-monitor/azure-sre-agent/samples/event-lab''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: code + fingerprint: 590387a44563f4dc1b107eec2272f90da068822ad2ffab1149e9930b78f5ef94 + count: 1 tables: - ffa178128112fd99f8d40cc82f4fd9fce361fbc8e3605f534aa3a0de7f44c89c: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: - The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now - use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state - gates, cost warning, scoring and cleanup remain. Current tables replacement begins: ''S1 | HTTP - 500 응답 | S1 — HTTP 500 장애''.' - b46d11954a35fe0afd5b627882274a683e78a850a5e724008791321d3a8aeb16: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: - The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now - use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state - gates, cost warning, scoring and cleanup remain. Current tables replacement begins: ''S2 | 주문 API - 지연 | S2 — 응답 지연''.' - 8b80f1ae4540feb17783ea900ad47f7e92cc74782a14e736d717219e45b327b6: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: - The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now - use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state - gates, cost warning, scoring and cleanup remain. Current tables replacement begins: ''S3 | Blob - 읽기 권한 제거 | S3 — Blob 권한 장애''.' - cc08d785e8dd9c540bb261bc5c1d481559a041087bc1d7a2f317b6dcb889d762: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: - The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now - use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state - gates, cost warning, scoring and cleanup remain. Current tables replacement begins: ''Agent가 스레드를 - 만들지 않음 | 에이전트 설정의 incident platform·응답 계획 확인''.' - 4c29553b4450e5b44b6e77e81286bd106bd9b811288a1cd6f78be94fc740bc5f: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: - The login/source and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Setup, baseline approval, scenario, results, troubleshooting and dynamic-threshold references now - use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, state - gates, cost warning, scoring and cleanup remain. Current tables replacement begins: ''채점이 INCOMPLETE - | 결과 채점의 수동 판정 절''.' + ffa178128112fd99f8d40cc82f4fd9fce361fbc8e3605f534aa3a0de7f44c89c: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: The login/source + and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Setup, + baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, + state gates, cost warning, scoring and cleanup remain. Current tables replacement begins: + ''S1 | HTTP 500 응답 | S1 — HTTP 500 장애''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: tables + fingerprint: 798148d0707575aec5bc0f94fd0be1044a7ad518330b79d6328788853882e405 + count: 1 + b46d11954a35fe0afd5b627882274a683e78a850a5e724008791321d3a8aeb16: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: The login/source + and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Setup, + baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, + state gates, cost warning, scoring and cleanup remain. Current tables replacement begins: + ''S2 | 주문 API 지연 | S2 — 응답 지연''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: tables + fingerprint: 02ad765a90d73afd95784d448fd37d8941bbb11af97b208d9b96d25f0051ea66 + count: 1 + 8b80f1ae4540feb17783ea900ad47f7e92cc74782a14e736d717219e45b327b6: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: The login/source + and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Setup, + baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, + state gates, cost warning, scoring and cleanup remain. Current tables replacement begins: + ''S3 | Blob 읽기 권한 제거 | S3 — Blob 권한 장애''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: tables + fingerprint: 36fce29b768c7dce85c51e59baf6aea4c72387f8c104e5dc6d0b14946601ab8d + count: 1 + cc08d785e8dd9c540bb261bc5c1d481559a041087bc1d7a2f317b6dcb889d762: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: The login/source + and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Setup, + baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, + state gates, cost warning, scoring and cleanup remain. Current tables replacement begins: + ''Agent가 스레드를 만들지 않음 | 에이전트 설정의 incident platform·응답 계획 확인''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: tables + fingerprint: b947367c044bf54c899ce4e16af2af9d76d6c46b5a6811c573dba5e5e4426631 + count: 1 + 4c29553b4450e5b44b6e77e81286bd106bd9b811288a1cd6f78be94fc740bc5f: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/event-lab/index.md: The login/source + and cd fences now use docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Setup, + baseline approval, scenario, results, troubleshooting and dynamic-threshold references now + use named canonical children rather than retired guide filenames. The three S1/S2/S3 faults, + state gates, cost warning, scoring and cleanup remain. Current tables replacement begins: + ''채점이 INCOMPLETE | 결과 채점의 수동 판정 절''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: tables + fingerprint: 38c1088b8a5da1716452b34a67c41d302b8acc97370314d9524e5a1316925f07 + count: 1 local_link_labels: - 22ebccef48adf3797ef14e253543c95913b641f5e0ac0482c5ab31694b8f74a0: Retained label '.devcontainer/README.md' - in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, pointing to .devcontainer/README.md. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 44028050825bcfa642b14ff4262d4da0b9393687cf19137c36484aa09ff58e8b: Replaced retired path label with - '에이전트 설정' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/setup/index.md. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 9e7735ab56a3c5447d3e3fced04c1b2fe3a2992cf3180f9ca2b688e7fee0ea64: Replaced retired path label with - '실측 검증 결과' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/validation-results/index.md. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 721d882d49a3cd517405ccc227fe908e4a862088f67bfde974795095f05c02b1: Replaced retired path label with - 'S1 — HTTP 500 장애' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, pointing to - docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md. The current canonical source - or owning-sample target was checked; this is a source-link/reader-label migration, not removal of - the referenced content. - 5c8bc528ec7649959420de6f956d39d8fe06deebea6a99112373148f0d0e814c: Replaced retired path label with - 'S2 — 응답 지연' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 1898c96d3ba8c474502f1860ec20522ea2883d180b44f744e7b6812908fba571: Replaced retired path label with - 'S3 — Blob 권한 장애' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, pointing to - docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md. The current canonical - source or owning-sample target was checked; this is a source-link/reader-label migration, not removal - of the referenced content. - d0352a09849221675c8996d4f85514fa1067158f15ca0a74d3747d499c347b55: Replaced retired path label with - '결과 채점' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/results/index.md. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - b342b4cce7c7903c2a29215e5442ef0eb9d9e5d166fbb3a3a5a1a8a280f6d399: Replaced retired path label with - 'Dynamic Thresholds 확장 설계' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, pointing - to docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md. The current canonical - source or owning-sample target was checked; this is a source-link/reader-label migration, not removal - of the referenced content. + 22ebccef48adf3797ef14e253543c95913b641f5e0ac0482c5ab31694b8f74a0: + missing_count: 2 + reason: Retained label '.devcontainer/README.md' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, + pointing to .devcontainer/README.md. The current canonical source or owning-sample target + was checked; this is a source-link/reader-label migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: prose + fingerprint: 917f2f46ef0ea50d5a7cd20f9c682bc367a935910f63ea1bee92ed35d7b3e340 + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: prose + fingerprint: c6e14773fa5ae771f0739befb2096f219e07dead76c904633142ded75f1499b7 + count: 1 + - kind: file + path: .devcontainer/README.md + sha256: 438713fac44d3fe76c97a5878a27575ce9ac2c64576ddba56ba1ea955fcdc998 + 44028050825bcfa642b14ff4262d4da0b9393687cf19137c36484aa09ff58e8b: + missing_count: 3 + reason: Replaced retired path label with '에이전트 설정' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/setup/index.md. The current canonical + source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: local_link_labels + fingerprint: 048413285eaddfca9b772fb935b9c55881d0e73e90a3cb90fb6e9afac425e872 + count: 3 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/setup/index.md + category: title + fingerprint: 0c2e310e43b2b80c67123ce079d1d5a0112f23678958ccf29004b869ee1286b6 + count: 1 + 9e7735ab56a3c5447d3e3fced04c1b2fe3a2992cf3180f9ca2b688e7fee0ea64: + missing_count: 1 + reason: Replaced retired path label with '실측 검증 결과' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/validation-results/index.md. The current + canonical source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: local_link_labels + fingerprint: 09e1dd0d8da0364fe4b0c5df6ff3cce1cbf1a395d8929d1ca3dcd1558eda8a3c + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: title + fingerprint: f76180846a8d104c7b8feac0a596c6d997866e148f63262b1c134162043ca7fc + count: 1 + 721d882d49a3cd517405ccc227fe908e4a862088f67bfde974795095f05c02b1: + missing_count: 1 + reason: Replaced retired path label with 'S1 — HTTP 500 장애' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md. The current + canonical source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: local_link_labels + fingerprint: 4de367fbe1f2fb3225642680178bb0f4472b28dc311e1bac2ce77a0c9ac4304b + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md + category: title + fingerprint: e32a47cbcbe792e95607534efe7d450a475dde792e18a3f8ac4abc5d0f935f99 + count: 1 + 5c8bc528ec7649959420de6f956d39d8fe06deebea6a99112373148f0d0e814c: + missing_count: 1 + reason: Replaced retired path label with 'S2 — 응답 지연' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md. The current + canonical source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: local_link_labels + fingerprint: c65505c3efde28e4ea22f2795c06a9bade96c6ef60e0f90797f1f17424ee8c85 + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md + category: title + fingerprint: b4d59aad0c2d13c3c8f8744bd04fc4bf127cb223194fe0fbabed88257b1ef5e9 + count: 1 + 1898c96d3ba8c474502f1860ec20522ea2883d180b44f744e7b6812908fba571: + missing_count: 1 + reason: Replaced retired path label with 'S3 — Blob 권한 장애' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: local_link_labels + fingerprint: 866bed7aed40500cd651714989e48969e9937ee34775b68892b094daa8674155 + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md + category: title + fingerprint: c90dcd7d518f37ad825833cff61808318928723b15dac70d17c8c4f88fef2234 + count: 1 + d0352a09849221675c8996d4f85514fa1067158f15ca0a74d3747d499c347b55: + missing_count: 2 + reason: Replaced retired path label with '결과 채점' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/results/index.md. The current canonical + source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: local_link_labels + fingerprint: b7c51db89a1538be2c4e2c6e27c40a343406dac73b5e63b4f049462e50ba0b42 + count: 2 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/results/index.md + category: title + fingerprint: 9adb1e924965dfde3be5ea5c982704e5b1caa168fa171ee2bb20b1eb003e167f + count: 1 + b342b4cce7c7903c2a29215e5442ef0eb9d9e5d166fbb3a3a5a1a8a280f6d399: + missing_count: 1 + reason: Replaced retired path label with 'Dynamic Thresholds 확장 설계' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md. The current + canonical source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: local_link_labels + fingerprint: 27fad307eb79228bedc9cff99543a519c2774bb003e773166c844b63565a231e + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md + category: title + fingerprint: 641fa5fe7671aee26dac81baabe5a609793a98d5c33d58fc2f7128dff7816012 + count: 1 - baseline_path: monitor/sre-agent-event-lab/dynamic-thresholds.md pages_path: guides/azure-monitor/sre-agent-dynamic-thresholds/index.md reviewed_changes: prose: - 014a10d01aa0e5fb54547492a3e2a67766e36db7efa6f0866894dba44914cdb5: 'In docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md: - The legacy Event path note still dates the Action Group/Logic App bridge to 2026-08-12 and says - it is not deployed by the current lab. Its reference is now 실측 검증 결과 at docs/services/azure-monitor/azure-sre-agent/validation-results/index.md - rather than the retired filename. Current prose replacement begins: ''- Event path(레거시 bridge): - Dynamic alert → Action Group → Logic App managed identity → SRE HTTP Trigger → Review-mode investigation. - 2026-08-''.' + 014a10d01aa0e5fb54547492a3e2a67766e36db7efa6f0866894dba44914cdb5: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md: The legacy + Event path note still dates the Action Group/Logic App bridge to 2026-08-12 and says it is + not deployed by the current lab. Its reference is now 실측 검증 결과 at docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + rather than the retired filename. Current prose replacement begins: ''- Event path(레거시 bridge): + Dynamic alert → Action Group → Logic App managed identity → SRE HTTP Trigger → Review-mode + investigation. 2026-08-''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md + category: prose + fingerprint: 8e09f39b24cd36114d409043cb2d364cf4b63478a912a16c8820844a0fc04523 + count: 1 local_link_labels: - 9e7735ab56a3c5447d3e3fced04c1b2fe3a2992cf3180f9ca2b688e7fee0ea64: Replaced retired path label with - '실측 검증 결과' in docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md, pointing - to docs/services/azure-monitor/azure-sre-agent/validation-results/index.md. The current canonical - source or owning-sample target was checked; this is a source-link/reader-label migration, not removal - of the referenced content. + 9e7735ab56a3c5447d3e3fced04c1b2fe3a2992cf3180f9ca2b688e7fee0ea64: + missing_count: 1 + reason: Replaced retired path label with '실측 검증 결과' in docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/validation-results/index.md. The current + canonical source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md + category: local_link_labels + fingerprint: 09e1dd0d8da0364fe4b0c5df6ff3cce1cbf1a395d8929d1ca3dcd1558eda8a3c + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: title + fingerprint: f76180846a8d104c7b8feac0a596c6d997866e148f63262b1c134162043ca7fc + count: 1 - baseline_path: monitor/sre-agent-event-lab/guides/01-agent-setup.md pages_path: labs/azure-monitor/sre-agent-event-lab-setup/index.md reviewed_changes: prose: - 24d42fb71e135de7ab48b81149ddb9e357fbe50028dd7fe668cb587d4725e108: 'In docs/services/azure-monitor/azure-sre-agent/setup/index.md: - The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - The Knowledge files row names the canonical incident-runbook and the next-step link names scenario-http-500. - The required roles, Review mode and baseline/setup state acknowledgements remain. Current prose - replacement begins: ''첫 장애를 주입합니다: S1 — HTTP 500 장애''.' + 24d42fb71e135de7ab48b81149ddb9e357fbe50028dd7fe668cb587d4725e108: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/setup/index.md: The environment fence + now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. The Knowledge files + row names the canonical incident-runbook and the next-step link names scenario-http-500. The + required roles, Review mode and baseline/setup state acknowledgements remain. Current prose + replacement begins: ''첫 장애를 주입합니다: S1 — HTTP 500 장애''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/setup/index.md + category: prose + fingerprint: de582471c65d52b73f956bf0a806ac870c851e84bd339104532965bf6f40838d + count: 1 code: - b052995a546e9e1f9ae8a5ab4d9dfd8f2dcbfb5e46c85107831eee8f5cb45f8e: 'In docs/services/azure-monitor/azure-sre-agent/setup/index.md: - The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - The Knowledge files row names the canonical incident-runbook and the next-step link names scenario-http-500. - The required roles, Review mode and baseline/setup state acknowledgements remain. Current code replacement - begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab source ./scripts/lab-env.sh''.' + b052995a546e9e1f9ae8a5ab4d9dfd8f2dcbfb5e46c85107831eee8f5cb45f8e: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/setup/index.md: The environment fence + now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. The Knowledge files + row names the canonical incident-runbook and the next-step link names scenario-http-500. The + required roles, Review mode and baseline/setup state acknowledgements remain. Current code + replacement begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab source + ./scripts/lab-env.sh''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/setup/index.md + category: code + fingerprint: 1e3c03a855ca5771d1335e37d91f68f1f22b05cfa8d892d324055bfedc6ef53e + count: 1 tables: - 3753279e936bb5b6e363f980fdbdec9b249ca829952b0bfff6a972580d0c6f1e: 'In docs/services/azure-monitor/azure-sre-agent/setup/index.md: - The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - The Knowledge files row names the canonical incident-runbook and the next-step link names scenario-http-500. - The required roles, Review mode and baseline/setup state acknowledgements remain. Current tables - replacement begins: ''Knowledge files | 인시던트 대응 런북 | 조사 순서와 금지 사항을 팀 규칙으로 강제합니다''.' + 3753279e936bb5b6e363f980fdbdec9b249ca829952b0bfff6a972580d0c6f1e: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/setup/index.md: The environment fence + now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. The Knowledge files + row names the canonical incident-runbook and the next-step link names scenario-http-500. The + required roles, Review mode and baseline/setup state acknowledgements remain. Current tables + replacement begins: ''Knowledge files | 인시던트 대응 런북 | 조사 순서와 금지 사항을 팀 규칙으로 강제합니다''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/setup/index.md + category: tables + fingerprint: 808c805c85cd82e00f67fa0af3f3d0261ed8e7dec10e0e07adb0b1424d6c4d52 + count: 1 local_link_labels: - 673435c83fe369747f909ad329e995ff4aab440b39b30b0967fb7c937a670950: Replaced retired path label with - '인시던트 대응 런북' in docs/services/azure-monitor/azure-sre-agent/setup/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/incident-runbook/index.md. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 1c3dbf23991386b472cb4d1276a30837f5c72ce8a85a554953faff7c54e156d3: Replaced retired path label with - 'S1 — HTTP 500 장애' in docs/services/azure-monitor/azure-sre-agent/setup/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. + 673435c83fe369747f909ad329e995ff4aab440b39b30b0967fb7c937a670950: + missing_count: 1 + reason: Replaced retired path label with '인시던트 대응 런북' in docs/services/azure-monitor/azure-sre-agent/setup/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/incident-runbook/index.md. The current + canonical source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/setup/index.md + category: local_link_labels + fingerprint: 005c706d567f8f581f62aac175df7f909e6b3eadda3da97332058e0d1e32684e + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/incident-runbook/index.md + category: title + fingerprint: dfbc98a263caec85142c7a6384e01137129864422a2bfc277e6b70971632b60a + count: 1 + 1c3dbf23991386b472cb4d1276a30837f5c72ce8a85a554953faff7c54e156d3: + missing_count: 1 + reason: Replaced retired path label with 'S1 — HTTP 500 장애' in docs/services/azure-monitor/azure-sre-agent/setup/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md. The current + canonical source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/setup/index.md + category: local_link_labels + fingerprint: 4de367fbe1f2fb3225642680178bb0f4472b28dc311e1bac2ce77a0c9ac4304b + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md + category: title + fingerprint: e32a47cbcbe792e95607534efe7d450a475dde792e18a3f8ac4abc5d0f935f99 + count: 1 - baseline_path: monitor/sre-agent-event-lab/guides/02-scenario-s1.md pages_path: labs/azure-monitor/sre-agent-scenario-http-500/index.md reviewed_changes: prose: - d0949831998e46a635c7028c64235af4300314bbe05fe974936b14e693d013d7: 'In docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md: - The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Start-condition and missing-thread advice point to setup, scoring points to results, and the next - step points to scenario-latency using reader-facing labels. HTTP 500 injection, state gates, capture - statuses and recovery commands remain. Current prose replacement begins: ''- 에이전트 설정을 마쳤고 evidence/state.json에 - baseline_passed와 agent_setup_acknowledged가 기록되어 있습니다.''.' - 7a6cc00434bf3af476835bccced55cbbde8de49ad9c89040d54cbf91981667f1: 'In docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md: - The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Start-condition and missing-thread advice point to setup, scoring points to results, and the next - step points to scenario-latency using reader-facing labels. HTTP 500 injection, state gates, capture - statuses and recovery commands remain. Current prose replacement begins: ''성공은 conclusion 하나뿐입니다. - 부분 성공은 결론이 나왔지만 내용이 얕은 경우이며, 이때도 상태는 conclusion이고 점수는 결과 채점에서 갈립니다. 빈 성공 화면을 만들지 않고 누락 상태와 마지막 확인 - 시각을 그대로 그림에 남깁니다.''.' - 602ac0a5e1349c2f535a8ead51ad2cc1a399a7531214aca08c336408060609d1: 'In docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md: - The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Start-condition and missing-thread advice point to setup, scoring points to results, and the next - step points to scenario-latency using reader-facing labels. HTTP 500 injection, state gates, capture - statuses and recovery commands remain. Current prose replacement begins: ''thread-not-created가 나오면 - 에이전트 설정의 incident platform과 응답 계획부터 다시 확인합니다.''.' - aeb020ec3047ebb43cecf025f3d2e755d82055561281b915692982956fbe9079: 'In docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md: - The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Start-condition and missing-thread advice point to setup, scoring points to results, and the next - step points to scenario-latency using reader-facing labels. HTTP 500 injection, state gates, capture - statuses and recovery commands remain. Current prose replacement begins: ''지연 장애로 넘어갑니다: S2 — 응답 - 지연''.' + d0949831998e46a635c7028c64235af4300314bbe05fe974936b14e693d013d7: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md: The environment + fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Start-condition + and missing-thread advice point to setup, scoring points to results, and the next step points + to scenario-latency using reader-facing labels. HTTP 500 injection, state gates, capture statuses + and recovery commands remain. Current prose replacement begins: ''- 에이전트 설정을 마쳤고 evidence/state.json에 + baseline_passed와 agent_setup_acknowledged가 기록되어 있습니다.''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md + category: prose + fingerprint: 98c383b512f361f6c4881866b09b6d13257fd447bbb42332a1e153c9d5bc8131 + count: 1 + 7a6cc00434bf3af476835bccced55cbbde8de49ad9c89040d54cbf91981667f1: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md: The environment + fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Start-condition + and missing-thread advice point to setup, scoring points to results, and the next step points + to scenario-latency using reader-facing labels. HTTP 500 injection, state gates, capture statuses + and recovery commands remain. Current prose replacement begins: ''성공은 conclusion 하나뿐입니다. 부분 + 성공은 결론이 나왔지만 내용이 얕은 경우이며, 이때도 상태는 conclusion이고 점수는 결과 채점에서 갈립니다. 빈 성공 화면을 만들지 않고 누락 상태와 마지막 + 확인 시각을 그대로 그림에 남깁니다.''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md + category: prose + fingerprint: 7e09809f09885a936d6fd0b3bac94c4e2928bf63fec7b2785b22e786d60f65ba + count: 1 + 602ac0a5e1349c2f535a8ead51ad2cc1a399a7531214aca08c336408060609d1: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md: The environment + fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Start-condition + and missing-thread advice point to setup, scoring points to results, and the next step points + to scenario-latency using reader-facing labels. HTTP 500 injection, state gates, capture statuses + and recovery commands remain. Current prose replacement begins: ''thread-not-created가 나오면 + 에이전트 설정의 incident platform과 응답 계획부터 다시 확인합니다.''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md + category: prose + fingerprint: 58a62a78b813deaafb3754bd6f6ae1e70f79108ef95c8d3c2eef85c592f49f1e + count: 1 + aeb020ec3047ebb43cecf025f3d2e755d82055561281b915692982956fbe9079: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md: The environment + fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Start-condition + and missing-thread advice point to setup, scoring points to results, and the next step points + to scenario-latency using reader-facing labels. HTTP 500 injection, state gates, capture statuses + and recovery commands remain. Current prose replacement begins: ''지연 장애로 넘어갑니다: S2 — 응답 지연''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md + category: prose + fingerprint: 7749d42bc6163170be716a5a0110293703a1359b8760c7490ba41bb477e0110c + count: 1 code: - b052995a546e9e1f9ae8a5ab4d9dfd8f2dcbfb5e46c85107831eee8f5cb45f8e: 'In docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md: - The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Start-condition and missing-thread advice point to setup, scoring points to results, and the next - step points to scenario-latency using reader-facing labels. HTTP 500 injection, state gates, capture - statuses and recovery commands remain. Current code replacement begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab - source ./scripts/lab-env.sh''.' + b052995a546e9e1f9ae8a5ab4d9dfd8f2dcbfb5e46c85107831eee8f5cb45f8e: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md: The environment + fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. Start-condition + and missing-thread advice point to setup, scoring points to results, and the next step points + to scenario-latency using reader-facing labels. HTTP 500 injection, state gates, capture statuses + and recovery commands remain. Current code replacement begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab + source ./scripts/lab-env.sh''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md + category: code + fingerprint: 1e3c03a855ca5771d1335e37d91f68f1f22b05cfa8d892d324055bfedc6ef53e + count: 1 local_link_labels: - 220ab3d6bd9f5bcf6b432aee6496d3d33dfc04f52e3166900819d49d0a33660f: Replaced retired path label with - '에이전트 설정' in docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md, pointing to - docs/services/azure-monitor/azure-sre-agent/setup/index.md. The current canonical source or owning-sample - target was checked; this is a source-link/reader-label migration, not removal of the referenced - content. - 642002bebe57cfb2d5d3168236cb52f8f261354812320f7c408f833079661570: Replaced retired path label with - '결과 채점' in docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/results/index.md. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 2c64e239550534f5e89f6403e8338cac2756d397dfbbce90d29af73440d01992: Replaced retired path label with - 'S2 — 응답 지연' in docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md, pointing - to docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md. The current canonical - source or owning-sample target was checked; this is a source-link/reader-label migration, not removal - of the referenced content. + 220ab3d6bd9f5bcf6b432aee6496d3d33dfc04f52e3166900819d49d0a33660f: + missing_count: 2 + reason: Replaced retired path label with '에이전트 설정' in docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/setup/index.md. The current canonical + source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md + category: local_link_labels + fingerprint: 048413285eaddfca9b772fb935b9c55881d0e73e90a3cb90fb6e9afac425e872 + count: 2 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/setup/index.md + category: title + fingerprint: 0c2e310e43b2b80c67123ce079d1d5a0112f23678958ccf29004b869ee1286b6 + count: 1 + 642002bebe57cfb2d5d3168236cb52f8f261354812320f7c408f833079661570: + missing_count: 1 + reason: Replaced retired path label with '결과 채점' in docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/results/index.md. The current canonical + source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md + category: local_link_labels + fingerprint: b7c51db89a1538be2c4e2c6e27c40a343406dac73b5e63b4f049462e50ba0b42 + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/results/index.md + category: title + fingerprint: 9adb1e924965dfde3be5ea5c982704e5b1caa168fa171ee2bb20b1eb003e167f + count: 1 + 2c64e239550534f5e89f6403e8338cac2756d397dfbbce90d29af73440d01992: + missing_count: 1 + reason: Replaced retired path label with 'S2 — 응답 지연' in docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md. The current + canonical source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md + category: local_link_labels + fingerprint: c65505c3efde28e4ea22f2795c06a9bade96c6ef60e0f90797f1f17424ee8c85 + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md + category: title + fingerprint: b4d59aad0c2d13c3c8f8744bd04fc4bf127cb223194fe0fbabed88257b1ef5e9 + count: 1 - baseline_path: monitor/sre-agent-event-lab/guides/03-scenario-s2.md pages_path: labs/azure-monitor/sre-agent-scenario-latency/index.md reviewed_changes: prose: - e4464baf5ec71e4bcbb043edc8a52d8e98d3fa260afffda990816f88c81a3ed7: 'In docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md: - The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - The prerequisites and next-step prose now name scenario-http-500 and scenario-blob-permission. S1 - recovery requirements, latency fault, conclusion capture gate and recovery commands remain. Current - prose replacement begins: ''- S1 — HTTP 500 장애의 S1이 복구되고 캡처가 conclusion으로 끝났습니다.''.' - c0da5ea550501e006069b2ce322ed03f38ad31fbe447ba4559cbda587b4162a3: 'In docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md: - The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - The prerequisites and next-step prose now name scenario-http-500 and scenario-blob-permission. S1 - recovery requirements, latency fault, conclusion capture gate and recovery commands remain. Current - prose replacement begins: ''권한 장애로 넘어갑니다: S3 — Blob 권한 장애''.' + e4464baf5ec71e4bcbb043edc8a52d8e98d3fa260afffda990816f88c81a3ed7: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md: The environment + fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. The prerequisites + and next-step prose now name scenario-http-500 and scenario-blob-permission. S1 recovery requirements, + latency fault, conclusion capture gate and recovery commands remain. Current prose replacement + begins: ''- S1 — HTTP 500 장애의 S1이 복구되고 캡처가 conclusion으로 끝났습니다.''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md + category: prose + fingerprint: cafc0311504aa98114ba8439623c04f49aad3c6c07b3843ee307c5045b7386cb + count: 1 + c0da5ea550501e006069b2ce322ed03f38ad31fbe447ba4559cbda587b4162a3: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md: The environment + fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. The prerequisites + and next-step prose now name scenario-http-500 and scenario-blob-permission. S1 recovery requirements, + latency fault, conclusion capture gate and recovery commands remain. Current prose replacement + begins: ''권한 장애로 넘어갑니다: S3 — Blob 권한 장애''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md + category: prose + fingerprint: 57365ca466c0a0252a149c6ccbafc80d1476e3fafd81936c57397a3ba1920613 + count: 1 code: - b052995a546e9e1f9ae8a5ab4d9dfd8f2dcbfb5e46c85107831eee8f5cb45f8e: 'In docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md: - The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - The prerequisites and next-step prose now name scenario-http-500 and scenario-blob-permission. S1 - recovery requirements, latency fault, conclusion capture gate and recovery commands remain. Current - code replacement begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab source - ./scripts/lab-env.sh''.' + b052995a546e9e1f9ae8a5ab4d9dfd8f2dcbfb5e46c85107831eee8f5cb45f8e: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md: The environment + fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. The prerequisites + and next-step prose now name scenario-http-500 and scenario-blob-permission. S1 recovery requirements, + latency fault, conclusion capture gate and recovery commands remain. Current code replacement + begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab source ./scripts/lab-env.sh''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md + category: code + fingerprint: 1e3c03a855ca5771d1335e37d91f68f1f22b05cfa8d892d324055bfedc6ef53e + count: 1 local_link_labels: - 1c3dbf23991386b472cb4d1276a30837f5c72ce8a85a554953faff7c54e156d3: Replaced retired path label with - 'S1 — HTTP 500 장애' in docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md, pointing - to docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md. The current canonical - source or owning-sample target was checked; this is a source-link/reader-label migration, not removal - of the referenced content. - 7d81587b0c8906bf8afea33f1abca69106523b68c0d0cd96c1371902e466f702: Replaced retired path label with - 'S3 — Blob 권한 장애' in docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md, pointing - to docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md. The current canonical - source or owning-sample target was checked; this is a source-link/reader-label migration, not removal - of the referenced content. + 1c3dbf23991386b472cb4d1276a30837f5c72ce8a85a554953faff7c54e156d3: + missing_count: 1 + reason: Replaced retired path label with 'S1 — HTTP 500 장애' in docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md. The current + canonical source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md + category: local_link_labels + fingerprint: 4de367fbe1f2fb3225642680178bb0f4472b28dc311e1bac2ce77a0c9ac4304b + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md + category: title + fingerprint: e32a47cbcbe792e95607534efe7d450a475dde792e18a3f8ac4abc5d0f935f99 + count: 1 + 7d81587b0c8906bf8afea33f1abca69106523b68c0d0cd96c1371902e466f702: + missing_count: 1 + reason: Replaced retired path label with 'S3 — Blob 권한 장애' in docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md + category: local_link_labels + fingerprint: 866bed7aed40500cd651714989e48969e9937ee34775b68892b094daa8674155 + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md + category: title + fingerprint: c90dcd7d518f37ad825833cff61808318928723b15dac70d17c8c4f88fef2234 + count: 1 - baseline_path: monitor/sre-agent-event-lab/guides/04-scenario-s3.md pages_path: labs/azure-monitor/sre-agent-scenario-blob-permission/index.md reviewed_changes: prose: - 3980a12d12b3d02cd5d1d39a9dbb621bb87a8e5719bb8496cf084a074b1cbe22: 'In docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md: - The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Prerequisite and next-step prose now name scenario-latency and results. Blob-role removal, S2 recovery/capture - gates and least-privilege restoration evidence remain. Current prose replacement begins: ''- S2 - — 응답 지연의 S2가 복구되고 캡처가 conclusion으로 끝났습니다.''.' - 8ffa7b1113e65afce2b1049c2ae54853457120e34024b5e7358d7a96eb991c68: 'In docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md: - The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Prerequisite and next-step prose now name scenario-latency and results. Blob-role removal, S2 recovery/capture - gates and least-privilege restoration evidence remain. Current prose replacement begins: ''수집한 근거를 - 채점합니다: 결과 채점''.' + 3980a12d12b3d02cd5d1d39a9dbb621bb87a8e5719bb8496cf084a074b1cbe22: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md: The + environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Prerequisite and next-step prose now name scenario-latency and results. Blob-role removal, + S2 recovery/capture gates and least-privilege restoration evidence remain. Current prose replacement + begins: ''- S2 — 응답 지연의 S2가 복구되고 캡처가 conclusion으로 끝났습니다.''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md + category: prose + fingerprint: 2a47996ce783158b2e5d88918c833a9ea7ae5c5c5011c72431ca561db225fe42 + count: 1 + 8ffa7b1113e65afce2b1049c2ae54853457120e34024b5e7358d7a96eb991c68: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md: The + environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Prerequisite and next-step prose now name scenario-latency and results. Blob-role removal, + S2 recovery/capture gates and least-privilege restoration evidence remain. Current prose replacement + begins: ''수집한 근거를 채점합니다: 결과 채점''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md + category: prose + fingerprint: 93e9b1a313e597e193690668b6101de5b6c226602528b82994b9100a74857a3f + count: 1 code: - b052995a546e9e1f9ae8a5ab4d9dfd8f2dcbfb5e46c85107831eee8f5cb45f8e: 'In docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md: - The environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. - Prerequisite and next-step prose now name scenario-latency and results. Blob-role removal, S2 recovery/capture - gates and least-privilege restoration evidence remain. Current code replacement begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab - source ./scripts/lab-env.sh''.' + b052995a546e9e1f9ae8a5ab4d9dfd8f2dcbfb5e46c85107831eee8f5cb45f8e: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md: The + environment fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab. + Prerequisite and next-step prose now name scenario-latency and results. Blob-role removal, + S2 recovery/capture gates and least-privilege restoration evidence remain. Current code replacement + begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab source ./scripts/lab-env.sh''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md + category: code + fingerprint: 1e3c03a855ca5771d1335e37d91f68f1f22b05cfa8d892d324055bfedc6ef53e + count: 1 local_link_labels: - 2c64e239550534f5e89f6403e8338cac2756d397dfbbce90d29af73440d01992: Replaced retired path label with - 'S2 — 응답 지연' in docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md, pointing - to docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md. The current canonical - source or owning-sample target was checked; this is a source-link/reader-label migration, not removal - of the referenced content. - 642002bebe57cfb2d5d3168236cb52f8f261354812320f7c408f833079661570: Replaced retired path label with - '결과 채점' in docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md, pointing - to docs/services/azure-monitor/azure-sre-agent/results/index.md. The current canonical source or - owning-sample target was checked; this is a source-link/reader-label migration, not removal of the - referenced content. + 2c64e239550534f5e89f6403e8338cac2756d397dfbbce90d29af73440d01992: + missing_count: 1 + reason: Replaced retired path label with 'S2 — 응답 지연' in docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md. The current + canonical source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md + category: local_link_labels + fingerprint: c65505c3efde28e4ea22f2795c06a9bade96c6ef60e0f90797f1f17424ee8c85 + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md + category: title + fingerprint: b4d59aad0c2d13c3c8f8744bd04fc4bf127cb223194fe0fbabed88257b1ef5e9 + count: 1 + 642002bebe57cfb2d5d3168236cb52f8f261354812320f7c408f833079661570: + missing_count: 1 + reason: Replaced retired path label with '결과 채점' in docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/results/index.md. The current canonical + source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md + category: local_link_labels + fingerprint: b7c51db89a1538be2c4e2c6e27c40a343406dac73b5e63b4f049462e50ba0b42 + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/results/index.md + category: title + fingerprint: 9adb1e924965dfde3be5ea5c982704e5b1caa168fa171ee2bb20b1eb003e167f + count: 1 - baseline_path: monitor/sre-agent-event-lab/guides/05-results.md pages_path: labs/azure-monitor/sre-agent-results/index.md reviewed_changes: code: - 67a6a1cd3fa2470fa487d0030975e2dcc8399fe8015dc21d9d02e112b6c00afe: 'In docs/services/azure-monitor/azure-sre-agent/results/index.md: - The scoring fence now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab before - invoking the unchanged app/.venv/bin/python scripts/score.py --evidence-root evidence command. Current - code replacement begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab app/.venv/bin/python - scripts/score.py --evidence-root evidence''.' + 67a6a1cd3fa2470fa487d0030975e2dcc8399fe8015dc21d9d02e112b6c00afe: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/results/index.md: The scoring fence + now enters docs/services/azure-monitor/azure-sre-agent/samples/event-lab before invoking the + unchanged app/.venv/bin/python scripts/score.py --evidence-root evidence command. Current + code replacement begins: ''cd docs/services/azure-monitor/azure-sre-agent/samples/event-lab + app/.venv/bin/python scripts/score.py --evidence-root evidence''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/results/index.md + category: code + fingerprint: c1f85dfd4ad2c382ac159ddfc21e9c89bd58c7266e2ac0e1f5036c7798d7d33d + count: 1 - baseline_path: monitor/sre-agent-event-lab/runbooks/incident-response.md pages_path: guides/azure-monitor/sre-agent-incident-runbook/index.md reviewed_changes: {} @@ -1380,104 +3553,290 @@ documents: pages_path: research/azure-monitor/sre-agent-validation-results/index.md reviewed_changes: prose: - 8271d6dbca2ffc2138a6910a441d84bf389105cd37f8657345b5645f9a8e918b: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: - Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured - outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example - names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. - Current execution instructions are explicitly linked to event-lab/index.md. Current prose replacement - begins: ''제품 개요와 실사용 패턴은 Azure SRE Agent 소개 자료를 먼저 참고한다. 이 문서는 S1/S2/S3 시나리오에서 측정한 수치, timeline, - evidence, 한계를 정리한다. 기록 시점 주의. 아래 결과는 azd 재구성 이전에 손으로 ''.' - 59eaa5e71ada1c5c2e7aa1c3f44779c0e9ced379a85d4255c9a6e91abdc1c509: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: - Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured - outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example - names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. - Current execution instructions are explicitly linked to event-lab/index.md. Current prose replacement - begins: ''- Agent thread: ''.' - 67c696e31c3eb32ecb73a7dff7650b346e0edbb471b2b6db308e7a5430673a3d: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: - Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured - outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example - names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. - Current execution instructions are explicitly linked to event-lab/index.md. Current prose replacement - begins: ''- Agent thread: ''.' - a147e49be39803c15924b5b1afa506dfcfd9db543e37dfc5f984aebf685aa96a: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: - Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured - outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example - names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. - Current execution instructions are explicitly linked to event-lab/index.md. Current prose replacement - begins: ''- Agent thread: ''.' - 8ad3fbef8612b932686e896eb20f5dbf33be2927c10d9f67dd72440f9359b068: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: - Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured - outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example - names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. - Current execution instructions are explicitly linked to event-lab/index.md. Current prose replacement - begins: ''후속 보정: vnet workload의 OTEL_SERVICE_NAME을 sre-event-lab-example로 변경하고 alert/evidence query도 - 이 deployment-unique 값으로 제한했다. 변경 후 새 role name으로 ''.' + 8271d6dbca2ffc2138a6910a441d84bf389105cd37f8657345b5645f9a8e918b: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: Safety: + the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured outcomes + and recovery limitation while replacing resource suffixes and thread GUIDs with example names + and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. + Current execution instructions are explicitly linked to event-lab/index.md. Current prose + replacement begins: ''제품 개요와 실사용 패턴은 Azure SRE Agent 소개 자료를 먼저 참고한다. 이 문서는 S1/S2/S3 시나리오에서 + 측정한 수치, timeline, evidence, 한계를 정리한다. 기록 시점 주의. 아래 결과는 azd 재구성 이전에 손으로 ''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: prose + fingerprint: 83f9d83eae1e08005fb765b63170c99021a5f243da5ee50d97bde624a04ca4c1 + count: 1 + 59eaa5e71ada1c5c2e7aa1c3f44779c0e9ced379a85d4255c9a6e91abdc1c509: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: Safety: + the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured outcomes + and recovery limitation while replacing resource suffixes and thread GUIDs with example names + and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. + Current execution instructions are explicitly linked to event-lab/index.md. Current prose + replacement begins: ''- Agent thread: ''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: prose + fingerprint: 05c215f003ac5cd5d823d6b788c1809b9c391d12c33f36a29450179c3ba95ed0 + count: 1 + 67c696e31c3eb32ecb73a7dff7650b346e0edbb471b2b6db308e7a5430673a3d: + missing_count: 1 + reason: 'Safety: the historical S2 Agent thread identifier is replaced by + in the corresponding scenario capture/evidence list in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md. + The approval binds the S2 row specifically; the 2026-08-12 observations, outcome and limitations + remain.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: prose + fingerprint: 7482e4b1e9974321cc361a76dd65d6a1c3d34abae00f236414cce4c87b05b416 + count: 1 + a147e49be39803c15924b5b1afa506dfcfd9db543e37dfc5f984aebf685aa96a: + missing_count: 1 + reason: 'Safety: the historical S3 Agent thread identifier is replaced by + in the corresponding scenario capture/evidence list in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md. + The approval binds the S3 row specifically; the 2026-08-12 observations, outcome and limitations + remain.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: prose + fingerprint: a9b7ce3b186784d8cc4eca9458e6483fe6b12c995e4f477799eb7c1c887ee7b4 + count: 1 + 8ad3fbef8612b932686e896eb20f5dbf33be2927c10d9f67dd72440f9359b068: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: Safety: + the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured outcomes + and recovery limitation while replacing resource suffixes and thread GUIDs with example names + and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. + Current execution instructions are explicitly linked to event-lab/index.md. Current prose + replacement begins: ''후속 보정: vnet workload의 OTEL_SERVICE_NAME을 sre-event-lab-example로 변경하고 + alert/evidence query도 이 deployment-unique 값으로 제한했다. 변경 후 새 role name으로 ''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: prose + fingerprint: 29efed84fcf4a95539246f992684601b4f033c78c33d04e197a7690a052d8a1b + count: 1 tables: - 909ff96fff1da5b1cecb3b0a52c0e9d9d9a28c7ac806be0b82f2a20a78b52dce: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: - Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured - outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example - names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. - Current execution instructions are explicitly linked to event-lab/index.md. Current tables replacement - begins: ''Log Analytics | law-sre-event-lab-example | ✅''.' - c019548adf1e89b3b9d0738c57fbb330eb8bc67f3e8616347a1ae7a28c3b0627: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: - Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured - outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example - names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. - Current execution instructions are explicitly linked to event-lab/index.md. Current tables replacement - begins: ''Application Insights | appi-sre-event-lab-example | ✅''.' - 009b1f97083a84aea95449b9595f90dc667b4d8de5276bcba9a0bba4daf6c550: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: - Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured - outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example - names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. - Current execution instructions are explicitly linked to event-lab/index.md. Current tables replacement - begins: ''Storage | stexample00000000, Blob private endpoint | ✅''.' - d7fd4a1cb28109a4524750adffd0bebe2ece8457de6d9df13dd49556d4134d11: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: - Safety: the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured - outcomes and recovery limitation while replacing resource suffixes and thread GUIDs with example - names and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. - Current execution instructions are explicitly linked to event-lab/index.md. Current tables replacement - begins: ''Agent | sre-devguidesample-example | ✅ Running''.' + 909ff96fff1da5b1cecb3b0a52c0e9d9d9a28c7ac806be0b82f2a20a78b52dce: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: Safety: + the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured outcomes + and recovery limitation while replacing resource suffixes and thread GUIDs with example names + and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. + Current execution instructions are explicitly linked to event-lab/index.md. Current tables + replacement begins: ''Log Analytics | law-sre-event-lab-example | ✅''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: tables + fingerprint: 9f5e7d67557d5f49d1a94ca626aa925ee15e4afdd056528061e61c99911782d6 + count: 1 + c019548adf1e89b3b9d0738c57fbb330eb8bc67f3e8616347a1ae7a28c3b0627: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: Safety: + the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured outcomes + and recovery limitation while replacing resource suffixes and thread GUIDs with example names + and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. + Current execution instructions are explicitly linked to event-lab/index.md. Current tables + replacement begins: ''Application Insights | appi-sre-event-lab-example | ✅''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: tables + fingerprint: 0741b213295f39071fca52fa3267d9a20bcc5c6d15c868d5c23bc8aab44c3be2 + count: 1 + 009b1f97083a84aea95449b9595f90dc667b4d8de5276bcba9a0bba4daf6c550: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: Safety: + the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured outcomes + and recovery limitation while replacing resource suffixes and thread GUIDs with example names + and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. + Current execution instructions are explicitly linked to event-lab/index.md. Current tables + replacement begins: ''Storage | stexample00000000, Blob private endpoint | ✅''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: tables + fingerprint: 816be4abf1532c9b0405d5813f84dada2c1b0e2475f09f24ead2a94a9f86922f + count: 1 + d7fd4a1cb28109a4524750adffd0bebe2ece8457de6d9df13dd49556d4134d11: + missing_count: 1 + reason: 'In docs/services/azure-monitor/azure-sre-agent/validation-results/index.md: Safety: + the 2026-08-12 historical results retain the manual-lab/legacy-bridge warning, measured outcomes + and recovery limitation while replacing resource suffixes and thread GUIDs with example names + and . The deployment-unique OTEL_SERVICE_NAME example is sre-event-lab-example. + Current execution instructions are explicitly linked to event-lab/index.md. Current tables + replacement begins: ''Agent | sre-devguidesample-example | ✅ Running''.' + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: tables + fingerprint: ea58fe7984e32f7043078917a77415cba0864104583cbd61ac45be43f933aa31 + count: 1 images: - 98692385d5dc253eebe2615d8858b1f470fc703c18faa1171834eca38339fe49: Preserved byte-for-byte in docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/investigation.gif - (SHA-256 3b130e64f7566503dd1a2706ccbf46bd9d9eeef2bc555e2cb57dcc6a42f72025); sample.yml publishes - it as validation-results/images/s1-investigation.gif to distinguish the three same-basename scenario - captures. Alt text is unchanged. - 94f1c44330f916dca1ce2dde914e40fcb8959302e54ab2d3bd90937cf15090bf: Preserved byte-for-byte in docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/investigation.gif - (SHA-256 4ff453aa973f9be400790974afe506d7b6628c81cbc6f69eaef5e4a9e28f0510); sample.yml publishes - it as validation-results/images/s2-investigation.gif to distinguish the three same-basename scenario - captures. Alt text is unchanged. - eb3e116af988c0bcb102a06972938519582b7f650db0897994ff117767ae0957: Preserved byte-for-byte in docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/investigation.gif - (SHA-256 5102eda3c2346968ea0deeae2e7cec434a864994f7ae869b377c12fc948a9eb5); sample.yml publishes - it as validation-results/images/s3-investigation.gif to distinguish the three same-basename scenario - captures. Alt text is unchanged. + 98692385d5dc253eebe2615d8858b1f470fc703c18faa1171834eca38339fe49: + missing_count: 1 + reason: Preserved byte-for-byte in docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/investigation.gif + (SHA-256 3b130e64f7566503dd1a2706ccbf46bd9d9eeef2bc555e2cb57dcc6a42f72025); sample.yml publishes + it as validation-results/images/s1-investigation.gif to distinguish the three same-basename + scenario captures. Alt text is unchanged. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: images + fingerprint: e35f8c6fd8ffa52bfd1e1c76253c16d19275044d523e36d14183aed020a2af46 + count: 1 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/investigation.gif + sha256: 3b130e64f7566503dd1a2706ccbf46bd9d9eeef2bc555e2cb57dcc6a42f72025 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/sample.yml + sha256: 944645876dfc81e72177178e1a029745ef255b7bfa93b69449f714677289c550 + 94f1c44330f916dca1ce2dde914e40fcb8959302e54ab2d3bd90937cf15090bf: + missing_count: 1 + reason: Preserved byte-for-byte in docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/investigation.gif + (SHA-256 4ff453aa973f9be400790974afe506d7b6628c81cbc6f69eaef5e4a9e28f0510); sample.yml publishes + it as validation-results/images/s2-investigation.gif to distinguish the three same-basename + scenario captures. Alt text is unchanged. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: images + fingerprint: c128197fa974cf57d96aeb1d69eb4448f75c58e516eaf126a72a5cefb6adbe70 + count: 1 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/investigation.gif + sha256: 4ff453aa973f9be400790974afe506d7b6628c81cbc6f69eaef5e4a9e28f0510 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/sample.yml + sha256: 944645876dfc81e72177178e1a029745ef255b7bfa93b69449f714677289c550 + eb3e116af988c0bcb102a06972938519582b7f650db0897994ff117767ae0957: + missing_count: 1 + reason: Preserved byte-for-byte in docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/investigation.gif + (SHA-256 5102eda3c2346968ea0deeae2e7cec434a864994f7ae869b377c12fc948a9eb5); sample.yml publishes + it as validation-results/images/s3-investigation.gif to distinguish the three same-basename + scenario captures. Alt text is unchanged. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: images + fingerprint: 9345e828b89a2c9fa75d25f348c9b336b77f38e6b2cc24d02464b980f2bff8df + count: 1 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/investigation.gif + sha256: 5102eda3c2346968ea0deeae2e7cec434a864994f7ae869b377c12fc948a9eb5 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/sample.yml + sha256: 944645876dfc81e72177178e1a029745ef255b7bfa93b69449f714677289c550 local_link_labels: - fabd7a655161bce48b915396b69df3bf2d77130e4bb0f5f9bbe4c626f890932a: Replaced retired path label with - '현재 실습 문서' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, pointing - to docs/services/azure-monitor/azure-sre-agent/event-lab/index.md. The current canonical source - or owning-sample target was checked; this is a source-link/reader-label migration, not removal of - the referenced content. - 176e60b305e09e5517119889b188d142890ad06562a56bee19e69327bc4f1709: Replaced retired path label with - '현재 실습 문서' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, pointing - to docs/services/azure-monitor/azure-sre-agent/event-lab/index.md. The current canonical source - or owning-sample target was checked; this is a source-link/reader-label migration, not removal of - the referenced content. - 4e4283a27c18d5d6aeb2633f6000f9c32ebd13743bcadb62eec08c3451132d55: Retained label '결론 frame' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, - pointing to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/07-conclusion.png; - docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/07-conclusion.png; - docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/07-conclusion.png. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 22267b2e1c53a41a357f022e22cd0b554d3413718ed88f6db4c317745ed9ac17: Retained label '실제 event timeline' - in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, pointing to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/timeline.md; - docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/timeline.md; docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.md. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 3efcbd689fedb88f8a060668e0f285de17f2e95ad652571306c7deda71ede7b1: Retained label 'Mermaid sequence - source' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, pointing to - docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/timeline.mmd; docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/timeline.mmd; - docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.mmd. The - current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. + fabd7a655161bce48b915396b69df3bf2d77130e4bb0f5f9bbe4c626f890932a: + missing_count: 1 + reason: Replaced retired path label with '현재 실습 문서' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/event-lab/index.md. The current canonical + source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: local_link_labels + fingerprint: 13b1e5aa9698261157458e87055433ea4098a13719f65b9e1898b1031d9a965a + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: title + fingerprint: d611d20ca77ec1bfca692ac1448fd34d1c59506dab94a52346a0b41750ee0364 + count: 1 + 176e60b305e09e5517119889b188d142890ad06562a56bee19e69327bc4f1709: + missing_count: 1 + reason: Replaced retired path label with '현재 실습 문서' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/event-lab/index.md. The current canonical + source or owning-sample target was checked; this is a source-link/reader-label migration, + not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: local_link_labels + fingerprint: 13b1e5aa9698261157458e87055433ea4098a13719f65b9e1898b1031d9a965a + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md + category: title + fingerprint: d611d20ca77ec1bfca692ac1448fd34d1c59506dab94a52346a0b41750ee0364 + count: 1 + 4e4283a27c18d5d6aeb2633f6000f9c32ebd13743bcadb62eec08c3451132d55: + missing_count: 3 + reason: Retained label '결론 frame' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/07-conclusion.png; + docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/07-conclusion.png; + docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/07-conclusion.png. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: prose + fingerprint: d4088c9a8e6e4233e1176ad0d61b80ac25462786ace36957cac48e2b79d7b229 + count: 3 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/07-conclusion.png + sha256: 11b0dd034a0bc72d841e347fd424b8a385340a22d8b04dc0601a0ea77a2d59e1 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/07-conclusion.png + sha256: 8141589fcdc74501123bc7ce8d5de41df20167e1946ae7c30d15416ae1c17b7c + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/07-conclusion.png + sha256: 61dfda52ab8a632c6984a7c8b56a16f06a73fd8dc18decdfa14f977d8879a727 + 22267b2e1c53a41a357f022e22cd0b554d3413718ed88f6db4c317745ed9ac17: + missing_count: 3 + reason: Retained label '실제 event timeline' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/timeline.md; + docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/timeline.md; + docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.md. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: prose + fingerprint: 2728d7b96640f4527ed5c389d1ce926a624bed99d5efb5200777d48a37211053 + count: 3 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/timeline.md + sha256: 23e41c5a1a3354a2a665b938e1ed5d289a4d3174c7d4aabb484419b965015d42 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/timeline.md + sha256: 2c994cfa4c36438d702e82017caab390bfd5534dcfaf471c02d62ba9936a1287 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.md + sha256: 59d7ea731503083635bcbd9196d02e33399214ed31807549e1878e467271229e + 3efcbd689fedb88f8a060668e0f285de17f2e95ad652571306c7deda71ede7b1: + missing_count: 3 + reason: Retained label 'Mermaid sequence source' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, + pointing to docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/timeline.mmd; + docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/timeline.mmd; + docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.mmd. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: prose + fingerprint: 57c926a2687c8505fda40c8c88bd468c271eda7422161b445a712f5a60bcc918 + count: 3 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/timeline.mmd + sha256: 5a5ded432982d0258fcd39b0a2bbd5fd9697e3ca8cde175b61c93474b7776cfd + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s2/timeline.mmd + sha256: cbf01cd4f59235aab08355a6c1a7103b973688a38b0896e8dd92cc56041cfa91 + - kind: file + path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.mmd + sha256: 0e615f6083752517db6f6d59389419a853ba37891ea70cabc63d1d8d08c94aa1 - baseline_path: mysql/nodejs/db_upgrade_zero_downtime.md pages_path: guides/azure-database-for-mysql/blue-green-upgrade/index.md reviewed_changes: {} @@ -1488,47 +3847,169 @@ documents: pages_path: guides/azure-openai/adaptive-ptu-load-balancing/index.md reviewed_changes: prose: - fe5088d17216c7a31ebc8e6a922bc8fb7daaf1b8f7f863e6f428a52d1bfc4092: 'In docs/services/azure-openai/adaptive-ptu-load-balancing/index.md: - The redundant 메인 문서: ptu-lb-test.md self-link was removed from 파일 구성; the same guide is this canonical - document. The editable diagram, renderer, Python controller, dependencies and optional PowerShell - implementation remain in docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks. - Current prose replacement begins: ''- 런북 잡이 워커에서 Completed로 종료''.' + fe5088d17216c7a31ebc8e6a922bc8fb7daaf1b8f7f863e6f428a52d1bfc4092: + missing_count: 1 + reason: 'In docs/services/azure-openai/adaptive-ptu-load-balancing/index.md: The redundant 메인 + 문서: ptu-lb-test.md self-link was removed from 파일 구성; the same guide is this canonical document. + The editable diagram, renderer, Python controller, dependencies and optional PowerShell implementation + remain in docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks. The bound + canonical title and 파일 구성 heading demonstrate the current document and navigation instead + of allowing an unbound self-link deletion.' + evidence: + - kind: structure + path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md + category: title + fingerprint: a4136a8c94dc8df9bab02145a638cdcdebeb62eeb6c21c4326de794c4fea16fb + count: 1 + - kind: structure + path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md + category: headings + fingerprint: 938c8cc107534cbde9d6d225d2ab9b537981d2c5ac282f37b9eae09fe1d1d522 + count: 1 local_link_labels: - 782c9f35e32cc1974dd512db107e29fca6fdf72fa3b9a230282acd141f20784c: In docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, - section "파일 구성" drops only the redundant link to this same guide; its title, problem statement and - content are retained at the canonical path. - d8340c1aaa9e95e871d626647970820cd4d84b2ebd67c0908548b3af1d912f75: Retained label 'ptu_architecture.excalidraw' - in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/ptu_architecture.excalidraw. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 3780ddb85f0f2468a666925639ce03196d37651ae07198d5f07178660b4cb73a: Retained label 'ptu_architecture.png' - in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/ptu_architecture.png. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - a44631d85720594c37c72f21f5d6086dd8e36ef4aa4df5d6ef8084402a49354b: Retained label 'render_excalidraw.py' - in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/render_excalidraw.py. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 7ee017639b4fc20bb4aa51df646d0915fee435c7d2b1fb7422ac484d9d035c99: Retained label 'runbooks/adaptive_tpm_quota_controller.py' - in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/adaptive_tpm_quota_controller.py. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 59e441dc4125b74571fdfbbe9d389c5f81a03e3b76542f71a4b770af7cbab02d: Retained label 'runbooks/requirements.txt' - in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/requirements.txt. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - 83d324c4a28e9157567e4fd24234bcd1f42e9974ac6778197bb506e0a84deda3: Retained label 'runbooks/optional_adaptive_tpm_quota_controller.ps1' - in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/optional_adaptive_tpm_quota_controller.ps1. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. + 782c9f35e32cc1974dd512db107e29fca6fdf72fa3b9a230282acd141f20784c: + missing_count: 1 + reason: In docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, section "파일 구성" + drops only the redundant link to this same guide; its title, problem statement and content + are retained at the canonical path. + evidence: + - kind: structure + path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md + category: title + fingerprint: a4136a8c94dc8df9bab02145a638cdcdebeb62eeb6c21c4326de794c4fea16fb + count: 1 + - kind: structure + path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md + category: headings + fingerprint: 938c8cc107534cbde9d6d225d2ab9b537981d2c5ac282f37b9eae09fe1d1d522 + count: 1 + d8340c1aaa9e95e871d626647970820cd4d84b2ebd67c0908548b3af1d912f75: + missing_count: 1 + reason: Retained label 'ptu_architecture.excalidraw' in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, + pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/ptu_architecture.excalidraw. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md + category: prose + fingerprint: 3e830073120fc23618766188ea6b70e020110f3dbe0dac31666df8b1179fe928 + count: 1 + - kind: file + path: docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/ptu_architecture.excalidraw + sha256: bc33797780dfd0e8c7e48851dab8664bfd6e859450cd38b560363197a67daea4 + 3780ddb85f0f2468a666925639ce03196d37651ae07198d5f07178660b4cb73a: + missing_count: 1 + reason: Retained label 'ptu_architecture.png' in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, + pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/ptu_architecture.png. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md + category: prose + fingerprint: 14d0d227e1084b1a72c7632456af41c566c110429826a37a786558cfd3f1263c + count: 1 + - kind: file + path: docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/ptu_architecture.png + sha256: accf9178699bee9407e694335ce1e975a7a8954338ec9ff4db73bbda27822ac4 + a44631d85720594c37c72f21f5d6086dd8e36ef4aa4df5d6ef8084402a49354b: + missing_count: 1 + reason: Retained label 'render_excalidraw.py' in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, + pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/render_excalidraw.py. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md + category: prose + fingerprint: f0908da93312da0f7632ed8e2a4dc82724d67d126b0e98bcd61e1cee6e7965de + count: 1 + - kind: file + path: docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/render_excalidraw.py + sha256: ebfaf59455909722b8d447042ac84a03428c4c301f3200bfa8c0b4a8024391ae + 7ee017639b4fc20bb4aa51df646d0915fee435c7d2b1fb7422ac484d9d035c99: + missing_count: 1 + reason: Retained label 'runbooks/adaptive_tpm_quota_controller.py' in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, + pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/adaptive_tpm_quota_controller.py. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md + category: prose + fingerprint: 1ef31cc4e68d401971b1ce6bc4b548d3e5f4b75f7066e62c8843c13ae427e89a + count: 1 + - kind: file + path: docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/adaptive_tpm_quota_controller.py + sha256: 391e561774bb963e90b88a78693c2aaf17708c84ea909244ad34a0a38e162f57 + 59e441dc4125b74571fdfbbe9d389c5f81a03e3b76542f71a4b770af7cbab02d: + missing_count: 1 + reason: Retained label 'runbooks/requirements.txt' in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, + pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/requirements.txt. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md + category: prose + fingerprint: 530a02263603ab44681201b88f5ee4a9e4fd721fe1431f503e256d9c9c483254 + count: 1 + - kind: file + path: docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/requirements.txt + sha256: 49656d26230e95250bb6c16864b4df682b929c08fa0858cd94873d87e69c7e40 + 83d324c4a28e9157567e4fd24234bcd1f42e9974ac6778197bb506e0a84deda3: + missing_count: 1 + reason: Retained label 'runbooks/optional_adaptive_tpm_quota_controller.ps1' in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, + pointing to docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/optional_adaptive_tpm_quota_controller.ps1. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md + category: prose + fingerprint: 39cf3ee98fc6179a6a40922c588e59e12ca835e0a4aa2ce817c2848780ebe57c + count: 1 + - kind: file + path: docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/optional_adaptive_tpm_quota_controller.ps1 + sha256: 3f8bb6d4736fd7b8672bc6c68d26cabbc18c67826460c711d17c94afbe162f71 - baseline_path: redis/nodejs/cluster_failover_recovery.md pages_path: cases/azure-managed-redis/cluster-failover-recovery/index.md reviewed_changes: local_link_labels: - 1b08bfef216eff6b14e0ccca69a06c7914498e7563355de0e674d34ee3cc9b7b: Retained label 'cluster_example/' - in docs/services/azure-managed-redis/cluster-failover-recovery/index.md, pointing to docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. + 1b08bfef216eff6b14e0ccca69a06c7914498e7563355de0e674d34ee3cc9b7b: + missing_count: 1 + reason: Retained label 'cluster_example/' in docs/services/azure-managed-redis/cluster-failover-recovery/index.md, + pointing to docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example. + The current canonical source or owning-sample target was checked; this is a source-link/reader-label + migration, not removal of the referenced content. + evidence: + - kind: structure + path: docs/services/azure-managed-redis/cluster-failover-recovery/index.md + category: prose + fingerprint: 7497a1325f7f9f9927207957ebcd4b861fe31b08cd25e66b9df7ccbe1d3c155d + count: 1 + - kind: file + path: docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/README.md + sha256: 1b862bf4da527e51ebd984cfde204d60ff161193cd3d19e505f63c1da48a4ab2 + - kind: file + path: docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/healthCheck.js + sha256: 3b83cfc3ed87412a90c51f280cee951be88765b845e105335e4540673caab0fb + - kind: file + path: docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/package-lock.json + sha256: db6deedfd1c7ce2be97ccf7c4dd7115c0982594779f511ce2f07e0c39b1417de + - kind: file + path: docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/package.json + sha256: 323678bf6c88d9f0da9a0e9508da1ddc6ba571c33df9c03788ebf1c8d305df66 + - kind: file + path: docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/redisClient.js + sha256: af691af75f520b1ef8f5b01dd622125dfe75ea55be3ceddaf9090a6102de0a6c + - kind: file + path: docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/routes.js + sha256: df1405faadeaa86335c29108e09a47e6ef06602e53b35c1edd4ece5ae21d22f5 + - kind: file + path: docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/server.js + sha256: bcba13f99b4ec31285b491659e26baee3fec98256e9cf068d248446ebfbdae96 dispositions: .azure/deployment-plan.md: status: excluded-local-state @@ -1555,9 +4036,11 @@ dispositions: current_paths: - docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_briefing_docs.py - tests/docs/test_reader_navigation.py - reason: Guide-layout assertions were replaced by canonical topic, navigation, and briefing-document contracts. + reason: Guide-layout assertions were replaced by canonical topic, navigation, and briefing-document + contracts. monitor/sre-agent-event-lab/scripts/tests/test_repo_readme.py: status: replaced-test current_paths: - docs/services/azure-monitor/azure-sre-agent/samples/event-lab/scripts/tests/test_repo_readme.py - reason: The repository discovery contract test moved with the sample and now targets canonical topic discovery. + reason: The repository discovery contract test moved with the sample and now targets canonical topic + discovery. diff --git a/tests/docs/test_pre_pages.py b/tests/docs/test_pre_pages.py index 5087f331..ac5bfc1d 100644 --- a/tests/docs/test_pre_pages.py +++ b/tests/docs/test_pre_pages.py @@ -63,7 +63,7 @@ def history(tmp_path: Path) -> tuple[Path, str, str]: @pytest.fixture def manifest_data() -> dict: return { - "version": 1, + "version": 2, "baseline_commit": BASELINE, "pages_commit": PAGES, "rename_similarity": 20, @@ -71,7 +71,17 @@ def manifest_data() -> dict: { "baseline_path": "old/guide.md", "pages_path": "guides/service/topic/index.md", - "reviewed_changes": {"prose": {FINGERPRINT: CONTENT_REASON}}, + "reviewed_changes": {"prose": {FINGERPRINT: { + "missing_count": 1, + "reason": CONTENT_REASON, + "evidence": [{ + "kind": "structure", + "path": "docs/services/service/topic/samples/evidence/README.md", + "category": "prose", + "fingerprint": "b" * 64, + "count": 1, + }], + }}}, } ], "dispositions": { @@ -155,7 +165,7 @@ def test_inventory_requires_all_top_level_fields( @pytest.mark.parametrize( ("field", "value"), [ - ("extra", True), ("version", 2), ("version", True), + ("extra", True), ("version", 1), ("version", True), ("rename_similarity", 0), ("rename_similarity", 101), ("rename_similarity", "20"), ("rename_similarity", True), ("documents", {}), ("dispositions", []), @@ -248,7 +258,7 @@ def test_inventory_reports_invalid_or_unsafe_yaml(tmp_path: Path, text: str) -> @pytest.fixture def raw_inventory() -> str: - return f"""version: 1 + return f"""version: 2 baseline_commit: {BASELINE} pages_commit: {PAGES} rename_similarity: 20 @@ -257,7 +267,15 @@ def raw_inventory() -> str: pages_path: guides/service/topic/index.md reviewed_changes: prose: - {FINGERPRINT}: {CONTENT_REASON} + {FINGERPRINT}: + missing_count: 1 + reason: {CONTENT_REASON} + evidence: + - kind: structure + path: docs/services/service/topic/samples/evidence/README.md + category: prose + fingerprint: {'b' * 64} + count: 1 dispositions: old/README.md: status: replaced-summary @@ -270,7 +288,7 @@ def raw_inventory() -> str: @pytest.mark.parametrize( ("original", "replacement", "key"), [ - ("version: 1", "version: 2\nversion: 1", "version"), + ("version: 2", "version: 1\nversion: 2", "version"), ( " - baseline_path: old/guide.md", " - baseline_path: ignored.md\n baseline_path: old/guide.md", @@ -282,8 +300,8 @@ def raw_inventory() -> str: "prose", ), ( - f" {FINGERPRINT}: {CONTENT_REASON}", - f" {FINGERPRINT}: Lost reason.\n {FINGERPRINT}: {CONTENT_REASON}", + f" {FINGERPRINT}:\n", + f" {FINGERPRINT}: {{}}\n {FINGERPRINT}:\n", FINGERPRINT, ), ( diff --git a/tests/docs/test_pre_pages_content.py b/tests/docs/test_pre_pages_content.py index eed5105d..1502d17d 100644 --- a/tests/docs/test_pre_pages_content.py +++ b/tests/docs/test_pre_pages_content.py @@ -1,15 +1,19 @@ from __future__ import annotations -from dataclasses import FrozenInstanceError +from dataclasses import FrozenInstanceError, replace from collections import Counter from hashlib import sha256 import json from pathlib import Path, PurePosixPath +import re +import shutil import pytest import yaml -from scripts.docs.pre_pages import AuditFormatError, git_text, load_inventory +from scripts.docs import pre_pages_content +from scripts.docs.content import load_document +from scripts.docs.pre_pages import AuditFormatError, BaselineDocument, git_text, load_inventory, validate_reviewed_changes from scripts.docs.pre_pages_content import ( DocumentPreservation, MarkdownStructure, @@ -34,11 +38,33 @@ def fingerprint(category: str, value: str) -> str: return sha256(f"{category}\0{value}".encode("utf-8")).hexdigest() -def compare(baseline: str, current: str, reviewed=None) -> DocumentPreservation: +def structure_evidence( + value: str = "Replacement.", category: str = "prose", count: int = 1, + path: str = str(CURRENT_PATH), +) -> dict: + return {"kind": "structure", "path": path, "category": category, "fingerprint": fingerprint(category, value), "count": count} + + +def approval(reason=REASON, missing_count=1, evidence=None) -> dict: + return { + "missing_count": missing_count, "reason": reason, + "evidence": [structure_evidence()] if evidence is None else evidence, + } + + +def proof_file(repo: Path, text: str = "Replacement.", path: str = str(CURRENT_PATH)) -> Path: + file = repo / path + file.parent.mkdir(parents=True, exist_ok=True) + file.write_text(text, encoding="utf-8") + return file + + +def compare(baseline: str, current: str, reviewed=None, *, repo_root=None) -> DocumentPreservation: return compare_markdown( BASELINE_PATH, CURRENT_PATH, extract_markdown_structure(baseline), extract_markdown_structure(current), reviewed or {}, + **({"repo_root": repo_root} if repo_root is not None else {}), ) @@ -153,6 +179,45 @@ def test_unterminated_fence_does_not_discard_historical_evidence() -> None: assert extract_markdown_structure("```text\nlast observation").code == ("text\nlast observation",) +@pytest.mark.parametrize("fence", ["```", "~~~"]) +@pytest.mark.parametrize("prefix", ["> ", "> > ", " >> "]) +def test_quoted_fences_keep_language_newlines_indentation_and_surrounding_prose( + fence: str, prefix: str, +) -> None: + text = ( + f"{prefix}Before.\n\n{prefix}{fence}python\n" + f"{prefix}if ready:\n{prefix} print('two spaces')\n" + f"{prefix}\n{prefix}> literal code prefix\n{prefix}{fence}\n\n{prefix}After." + ) + structure = extract_markdown_structure(text) + assert structure.code == ("python\nif ready:\n print('two spaces')\n\n> literal code prefix",) + assert structure.prose == ("Before.", "After.") + + +def test_quoted_fence_inside_indented_list_container() -> None: + structure = extract_markdown_structure( + "- Example:\n\n > ```bash\n > run first\n > run second\n > ```\n\nAfter." + ) + assert structure.code == ("bash\nrun first\n run second",) + assert structure.prose == ("- Example:", "After.") + + +def test_quoted_code_does_not_consume_prose_after_its_container_ends() -> None: + structure = extract_markdown_structure("> ```sh\n> run\n\nOutside paragraph.") + assert structure.code == ("sh\nrun",) + assert structure.prose == ("Outside paragraph.",) + + +def test_splitting_the_real_argocd_redis_command_changes_its_code_fingerprint() -> None: + path = ROOT / "docs/services/azure-kubernetes-service/argocd-image-updater-acr/index.md" + text = path.read_text(encoding="utf-8") + command = "kubectl set image deployment/argocd-redis redis=redis:8.2.3-alpine -n argocd" + assert f"> {command}" in text + broken = text.replace(f"> {command}", "> kubectl set image\n> deployment/argocd-redis redis=redis:8.2.3-alpine -n argocd") + result = compare(text, broken) + assert StructureFinding("code", fingerprint("code", f"bash\n{command}"), f"bash\n{command}") in result.missing + + def test_table_separators_are_ignored_but_all_rows_remain() -> None: before = "| Header | 값 |\n|:---|---:|\n| A | 10 |\n| B | 20 |\n" after = "Header|값\n-----|-----\n A | 10\n B | 20\n" @@ -226,55 +291,59 @@ def test_duplicate_historical_structures_require_matching_multiplicity() -> None assert result.missing[0].fingerprint == fingerprint("prose", "Repeated.") -def test_reviewed_exception_applies_only_to_the_exact_fingerprint() -> None: - result = compare("Removed.\n\nOther.", "", {"prose": {fingerprint("prose", "Removed."): REASON}}) +def test_reviewed_exception_applies_only_to_the_exact_fingerprint(tmp_path: Path) -> None: + proof_file(tmp_path) + result = compare("Removed.\n\nOther.", "Replacement.", {"prose": {fingerprint("prose", "Removed."): approval()}}, repo_root=tmp_path) assert [finding.excerpt for finding in result.reviewed] == ["Removed."] assert [finding.excerpt for finding in result.missing] == ["Other."] -def test_reviewed_status_requires_no_unreviewed_missing_structure() -> None: - result = compare("Removed.", "", {"prose": {fingerprint("prose", "Removed."): REASON}}) +def test_reviewed_status_requires_no_unreviewed_missing_structure(tmp_path: Path) -> None: + proof_file(tmp_path) + result = compare("Removed.", "Replacement.", {"prose": {fingerprint("prose", "Removed."): approval()}}, repo_root=tmp_path) assert result.status == "reviewed" assert not result.missing and len(result.reviewed) == 1 def test_wrong_exception_category_is_stale_not_a_cross_category_approval() -> None: with pytest.raises(AuditFormatError, match="stale"): - compare("## Heading", "", {"prose": {fingerprint("headings", "Heading"): REASON}}) + compare("## Heading", "", {"prose": {fingerprint("headings", "Heading"): approval()}}) def test_stale_exception_is_an_inventory_error_even_after_a_block_is_restored() -> None: with pytest.raises(AuditFormatError, match="stale"): - compare("Kept.", "Kept.", {"prose": {fingerprint("prose", "Kept."): REASON}}) + compare("Kept.", "Kept.", {"prose": {fingerprint("prose", "Kept."): approval()}}) @pytest.mark.parametrize("reason", ["", " ", None, "updated", "not needed", "Preserved in sample README."]) def test_exceptions_reject_empty_or_generic_reasons(reason: object) -> None: with pytest.raises(AuditFormatError, match="reason"): - compare("Removed.", "", {"prose": {fingerprint("prose", "Removed."): reason}}) + compare("Removed.", "", {"prose": {fingerprint("prose", "Removed."): approval(reason)}}) def test_a_path_alone_does_not_make_a_generic_updated_reason_specific() -> None: with pytest.raises(AuditFormatError, match="reason"): compare( "Removed.", "", - {"prose": {fingerprint("prose", "Removed."): "Updated docs/services/service/topic/index.md."}}, + {"prose": {fingerprint("prose", "Removed."): approval("Updated docs/services/service/topic/index.md.")}}, ) -def test_specific_korean_preservation_reasons_are_valid() -> None: +def test_specific_korean_preservation_reasons_are_valid(tmp_path: Path) -> None: reason = ( "docs/services/service/topic/samples/evidence/README.md의 이관 전 조사 명령 절에 " "원문을 보존하고 현재 가이드에서 그 역사적 증거로 연결한다." ) - result = compare("Removed.", "", {"prose": {fingerprint("prose", "Removed."): reason}}) + proof_file(tmp_path) + result = compare("Removed.", "Replacement.", {"prose": {fingerprint("prose", "Removed."): approval(reason)}}, repo_root=tmp_path) assert result.status == "reviewed" @pytest.mark.parametrize("key", ["hash", "A" * 64, "g" * 64, "a" * 63, 1]) def test_inventory_rejects_non_sha256_exception_keys(tmp_path: Path, key: object) -> None: data = yaml.safe_load((ROOT / "scripts/docs/pre_pages_inventory.yml").read_text()) - data["documents"][0]["reviewed_changes"] = {"prose": {key: REASON}} + data["version"] = 2 + data["documents"][0]["reviewed_changes"] = {"prose": {key: approval()}} path = tmp_path / "inventory.yml" path.write_text(yaml.safe_dump(data, allow_unicode=True), encoding="utf-8") with pytest.raises(AuditFormatError, match="fingerprint"): @@ -283,7 +352,8 @@ def test_inventory_rejects_non_sha256_exception_keys(tmp_path: Path, key: object def test_inventory_rejects_unknown_exception_categories(tmp_path: Path) -> None: data = yaml.safe_load((ROOT / "scripts/docs/pre_pages_inventory.yml").read_text()) - data["documents"][0]["reviewed_changes"] = {"anything": {"a" * 64: REASON}} + data["version"] = 2 + data["documents"][0]["reviewed_changes"] = {"anything": {"a" * 64: approval()}} path = tmp_path / "inventory.yml" path.write_text(yaml.safe_dump(data, allow_unicode=True), encoding="utf-8") with pytest.raises(AuditFormatError, match="category"): @@ -353,3 +423,280 @@ def test_historical_reader_map_preserves_all_six_detailed_audience_rows_in_ownin (topic / "index.md").read_text(encoding="utf-8") ) assert "not a current product-support statement" in sample.read_text(encoding="utf-8") + + +def isolated_real_document_audit( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, baseline: str, current: str, +): + inventory = load_inventory(ROOT / "scripts/docs/pre_pages_inventory.yml") + entry = next(doc for doc in inventory.documents if str(doc.baseline_path) == baseline) + current_path = tmp_path / current + shutil.copytree((ROOT / current).parent, current_path.parent) + monkeypatch.setattr( + pre_pages_content, "resolve_current_documents", + lambda repo, manifest: {entry.baseline_path: load_document(current_path, tmp_path / "docs")}, + ) + monkeypatch.setattr(pre_pages_content, "git_text", lambda repo, commit, path: git_text(ROOT, commit, path)) + return replace(inventory, documents=(entry,)), current_path + + +def test_deleting_current_nginx_fence_invalidates_its_real_baseline_approval( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, +) -> None: + inventory, path = isolated_real_document_audit( + tmp_path, monkeypatch, "aifoundry/codex_closednetwork.md", + "docs/services/microsoft-foundry/codex-closed-network/index.md", + ) + assert not audit_document_content(tmp_path, inventory)[0].missing + text, removed = re.subn(r"```nginx\n.*?\n```", "", path.read_text(), flags=re.DOTALL) + assert removed == 1 + path.write_text(text, encoding="utf-8") + with pytest.raises(AuditFormatError, match="evidence"): + audit_document_content(tmp_path, inventory) + + +def test_agent_memory_second_missing_06_link_cannot_reuse_one_occurrence_approval( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, +) -> None: + inventory, path = isolated_real_document_audit( + tmp_path, monkeypatch, "memory/agent-memory/README.md", + "docs/services/microsoft-foundry/agent-memory/index.md", + ) + assert not audit_document_content(tmp_path, inventory)[0].missing + text = path.read_text() + link = "[06. 커머스 적용 설계](commerce/index.md)" + assert text.count(link) == 1 + path.write_text(text.replace(link, "06. 커머스 적용 설계"), encoding="utf-8") + with pytest.raises(AuditFormatError, match="missing_count"): + audit_document_content(tmp_path, inventory) + + +def test_generic_safety_prefix_does_not_justify_a_deletion() -> None: + with pytest.raises(AuditFormatError, match="reason"): + compare("Removed.", "", { + "prose": {fingerprint("prose", "Removed."): approval("Safety: updated and no longer needed for this content.")}, + }) + + +def test_structured_approvals_and_references_are_deeply_immutable() -> None: + raw = approval() + record = BaselineDocument(BASELINE_PATH, PurePosixPath("guides/service/topic/index.md"), { + "prose": {fingerprint("prose", "Removed."): raw}, + }) + change = record.reviewed_changes["prose"][fingerprint("prose", "Removed.")] + assert change.missing_count == 1 and change.reason == REASON + assert change.evidence[0].path == CURRENT_PATH + raw["evidence"][0]["count"] = 9 + assert change.evidence[0].count == 1 + with pytest.raises(FrozenInstanceError): + change.missing_count = 2 + with pytest.raises(FrozenInstanceError): + change.evidence[0].count = 2 + + +def test_bare_reason_strings_are_no_longer_approvals() -> None: + with pytest.raises(AuditFormatError, match="approval"): + validate_reviewed_changes({"prose": {fingerprint("prose", "Removed."): REASON}}) + + +@pytest.mark.parametrize("field", ["missing_count", "reason", "evidence"]) +def test_approval_requires_all_binding_fields(field: str) -> None: + change = approval() + del change[field] + with pytest.raises(AuditFormatError, match=field): + validate_reviewed_changes({"prose": {fingerprint("prose", "Removed."): change}}) + + +@pytest.mark.parametrize("count", [0, -1, True, 1.5, "1", None]) +def test_missing_count_must_be_a_positive_integer(count: object) -> None: + with pytest.raises(AuditFormatError, match="missing_count"): + validate_reviewed_changes({"prose": {fingerprint("prose", "Removed."): approval(missing_count=count)}}) + + +@pytest.mark.parametrize("references", [[], {}, "current.md", None]) +def test_approval_requires_a_nonempty_evidence_sequence(references: object) -> None: + change = approval() + change["evidence"] = references + with pytest.raises(AuditFormatError, match="evidence"): + validate_reviewed_changes({"prose": {fingerprint("prose", "Removed."): change}}) + + +@pytest.mark.parametrize( + ("field", "value"), + [ + ("kind", "unknown"), ("kind", None), ("category", "unknown"), + ("fingerprint", "sha"), ("fingerprint", "A" * 64), + ("count", 0), ("count", -1), ("count", True), ("count", 1.5), ("count", "1"), + ("path", "/outside.md"), ("path", "../outside.md"), ("path", "a/../outside.md"), + ("path", "C:/outside.md"), ("path", "a\\outside.md"), ("path", ""), + ], +) +def test_invalid_structural_reference_fields_are_rejected(field: str, value: object) -> None: + ref = structure_evidence() + ref[field] = value + with pytest.raises(AuditFormatError, match=field): + validate_reviewed_changes({"prose": {fingerprint("prose", "Removed."): approval(evidence=[ref])}}) + + +@pytest.mark.parametrize("field", ["path", "category", "fingerprint", "count", "kind"]) +def test_structural_reference_requires_all_fields(field: str) -> None: + ref = structure_evidence() + del ref[field] + with pytest.raises(AuditFormatError, match=field): + validate_reviewed_changes({"prose": {fingerprint("prose", "Removed."): approval(evidence=[ref])}}) + + +@pytest.mark.parametrize("kind", ["structure", "file"]) +def test_duplicate_references_are_rejected_even_if_counts_or_hashes_differ(kind: str) -> None: + if kind == "structure": + first, second = structure_evidence(), structure_evidence(count=2) + else: + first = {"kind": "file", "path": "docs/evidence.bin", "sha256": "a" * 64} + second = {**first, "sha256": "b" * 64} + with pytest.raises(AuditFormatError, match="duplicate"): + validate_reviewed_changes({"prose": {fingerprint("prose", "Removed."): approval(evidence=[first, second])}}) + + +@pytest.mark.parametrize( + "reference", + [ + {"kind": "file", "path": "docs/evidence.bin"}, + {"kind": "file", "path": "docs/evidence.bin", "sha256": "A" * 64}, + {"kind": "file", "path": "../evidence.bin", "sha256": "a" * 64}, + {"kind": "file", "path": "docs/evidence.bin", "sha256": "a" * 64, "count": 1}, + ], +) +def test_invalid_file_evidence_is_rejected(reference: dict) -> None: + with pytest.raises(AuditFormatError, match="evidence"): + validate_reviewed_changes({"prose": {fingerprint("prose", "Removed."): approval(evidence=[reference])}}) + + +@pytest.mark.parametrize( + "reason", + [ + "Safety: updated and no longer needed for this content.", + "Safety: updated during migration and not needed anymore.", + "Review: this document was revised and approved after migration.", + "Reason: Safety: Updated and removed because this document is no longer needed.", + "Preservation: updated docs/services/service/topic/index.md.", + "안전: 이 내용은 더 이상 필요하지 않아서 업데이트하고 삭제했습니다.", + "검토: 해당 문서를 최신 내용으로 갱신하고 불필요한 내용을 삭제했습니다.", + "보존: docs/services/service/topic/index.md 문서의 내용을 업데이트했습니다.", + "안전: 기존 내용에 대한 검토를 거쳐서 불필요한 부분을 적절하게 삭제했습니다.", + "사유: 이전 문서의 이관 과정에서 일반적으로 더 이상 쓰이지 않는 항목을 정리했습니다.", + ], +) +def test_generic_only_reasons_cannot_hide_behind_english_or_korean_prefixes(reason: str) -> None: + with pytest.raises(AuditFormatError, match="reason"): + validate_reviewed_changes({"prose": {fingerprint("prose", "Removed."): approval(reason)}}) + + +@pytest.mark.parametrize( + "reason", + [ + "The nginx reverse-proxy Host header uses the example domain while retaining the upstream port.", + "Nginx 프록시의 Host 헤더를 예시 도메인으로 치환하고 기존 upstream 포트와 전달 설정을 보존한다.", + ], +) +def test_concrete_reasons_do_not_need_to_repeat_paths_already_bound_as_evidence(reason: str) -> None: + parsed = validate_reviewed_changes({"prose": {fingerprint("prose", "Removed."): approval(reason)}}) + assert parsed["prose"][fingerprint("prose", "Removed.")].reason == reason + + +def test_one_missing_occurrence_approval_cannot_clear_two(tmp_path: Path) -> None: + proof_file(tmp_path) + reviewed = {"prose": {fingerprint("prose", "Removed."): approval(missing_count=1)}} + with pytest.raises(AuditFormatError, match="missing_count"): + compare("Removed.\n\nRemoved.", "Replacement.", reviewed, repo_root=tmp_path) + + +def test_exact_missing_occurrence_count_is_accepted(tmp_path: Path) -> None: + proof_file(tmp_path) + reviewed = {"prose": {fingerprint("prose", "Removed."): approval(missing_count=2)}} + result = compare("Removed.\n\nRemoved.", "Replacement.", reviewed, repo_root=tmp_path) + assert not result.missing and len(result.reviewed) == 2 + + +def test_approvals_cannot_skip_current_evidence_verification_by_omitting_repo_root() -> None: + with pytest.raises(AuditFormatError, match="repo_root"): + compare("Removed.", "Replacement.", {"prose": {fingerprint("prose", "Removed."): approval()}}) + + +@pytest.mark.parametrize("mutation", ["delete", "change", "duplicate"]) +def test_structure_evidence_is_reread_and_checked_on_every_comparison( + tmp_path: Path, mutation: str, +) -> None: + file = proof_file(tmp_path) + reviewed = {"prose": {fingerprint("prose", "Removed."): approval()}} + assert compare("Removed.", "Replacement.", reviewed, repo_root=tmp_path).status == "reviewed" + if mutation == "delete": + file.unlink() + else: + file.write_text("Changed." if mutation == "change" else "Replacement.\n\nReplacement.") + with pytest.raises(AuditFormatError, match="evidence"): + compare("Removed.", "Replacement.", reviewed, repo_root=tmp_path) + + +def test_evidence_occurrence_count_requires_the_exact_current_multiplicity(tmp_path: Path) -> None: + file = proof_file(tmp_path, "Replacement.\n\nReplacement.") + reviewed = {"prose": {fingerprint("prose", "Removed."): approval(evidence=[structure_evidence(count=2)])}} + assert compare("Removed.", file.read_text(), reviewed, repo_root=tmp_path).status == "reviewed" + file.write_text("Replacement.") + with pytest.raises(AuditFormatError, match="count"): + compare("Removed.", file.read_text(), reviewed, repo_root=tmp_path) + + +@pytest.mark.parametrize("mutation", ["change", "delete"]) +def test_binary_file_evidence_requires_the_exact_current_sha256(tmp_path: Path, mutation: str) -> None: + path = tmp_path / "evidence.bin" + original = b"\x00\xffhistorical capture" + path.write_bytes(original) + ref = {"kind": "file", "path": "evidence.bin", "sha256": sha256(original).hexdigest()} + reviewed = {"prose": {fingerprint("prose", "Removed."): approval(evidence=[ref])}} + assert compare("Removed.", "", reviewed, repo_root=tmp_path).status == "reviewed" + if mutation == "delete": + path.unlink() + else: + path.write_bytes(b"\x00changed") + with pytest.raises(AuditFormatError, match="evidence"): + compare("Removed.", "", reviewed, repo_root=tmp_path) + + +@pytest.mark.parametrize("kind", ["structure", "file"]) +def test_evidence_cannot_escape_the_repository_through_a_symlink(tmp_path: Path, kind: str) -> None: + outside = tmp_path.parent / f"{tmp_path.name}-outside.md" + outside.write_text("Replacement.") + (tmp_path / "link.md").symlink_to(outside) + ref = structure_evidence(path="link.md") if kind == "structure" else { + "kind": "file", "path": "link.md", "sha256": sha256(outside.read_bytes()).hexdigest(), + } + with pytest.raises(AuditFormatError, match="outside"): + compare("Removed.", "", {"prose": {fingerprint("prose", "Removed."): approval(evidence=[ref])}}, repo_root=tmp_path) + + +def test_subscription_row_approval_binds_the_subscription_not_an_unrelated_public_ip_row() -> None: + inventory = load_inventory(ROOT / "scripts/docs/pre_pages_inventory.yml") + entry = next(d for d in inventory.documents if str(d.baseline_path) == "monitor/otel-monitor-setup.md") + baseline = extract_markdown_structure(git_text(ROOT, inventory.baseline_commit, entry.baseline_path)) + row = next(row for row in baseline.tables if row.startswith("구독 |")) + change = entry.reviewed_changes["tables"][fingerprint("tables", row)] + assert any( + ref.kind == "structure" and ref.category == "tables" + and ref.fingerprint == fingerprint("tables", "구독 | ") + for ref in change.evidence + ) + + +def test_each_historical_sre_thread_approval_binds_its_corresponding_scenario() -> None: + inventory = load_inventory(ROOT / "scripts/docs/pre_pages_inventory.yml") + entry = next(d for d in inventory.documents if str(d.baseline_path) == "monitor/sre-agent-event-lab/validation-results.md") + baseline = extract_markdown_structure(git_text(ROOT, inventory.baseline_commit, entry.baseline_path)) + threads = [value for value in baseline.prose if value.startswith("- Agent thread:")] + assert len(threads) == 3 + for scenario, value in enumerate(threads, 1): + change = entry.reviewed_changes["prose"][fingerprint("prose", value)] + assert any( + ref.kind == "structure" and ref.category == "prose" + and ref.fingerprint == fingerprint("prose", f"- Agent thread: ") + for ref in change.evidence + ) From 7bfeab63fb303a2d6b21445afb15ceaa15ae189c Mon Sep 17 00:00:00 2001 From: hellices Date: Mon, 14 Sep 2026 02:07:19 +0900 Subject: [PATCH 07/26] fix(docs): require exact links and public tracked evidence Bind replacement-link approvals to explicit current destinations and counts, allowing only verified redundant self-link removals. Restrict all evidence to public HEAD-tracked nonignored sources and require concrete reasons naming exact evidence paths. Recheck all 235 approvals and retain the historical reader-map restoration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/docs/pre_pages.py | 124 ++++++- scripts/docs/pre_pages_content.py | 113 ++++++- scripts/docs/pre_pages_inventory.yml | 473 ++++++++++++++++----------- tests/docs/test_pre_pages.py | 8 +- tests/docs/test_pre_pages_content.py | 460 +++++++++++++++++++++++++- 5 files changed, 943 insertions(+), 235 deletions(-) diff --git a/scripts/docs/pre_pages.py b/scripts/docs/pre_pages.py index 2f90f798..9cc41bef 100644 --- a/scripts/docs/pre_pages.py +++ b/scripts/docs/pre_pages.py @@ -68,6 +68,7 @@ class ReviewedChange: missing_count: int reason: str evidence: tuple[StructureEvidence | FileEvidence, ...] + link_change: str | None = None @dataclass(frozen=True) @@ -156,6 +157,17 @@ def _relative_path(value: object, label: str) -> PurePosixPath: STRUCTURE_CATEGORIES = ( "title", "headings", "prose", "code", "tables", "images", "local_link_labels", ) +EVIDENCE_CATEGORIES = (*STRUCTURE_CATEGORIES, "links") + + +def validate_public_evidence_path(path: PurePosixPath, label: str) -> None: + if any(part.casefold() in {".git", ".superpowers"} for part in path.parts): + raise AuditFormatError(f"{label}: forbidden evidence path: {path}") + if not ( + len(path.parts) > 2 and path.parts[:2] == ("docs", "services") + or path == PurePosixPath(".devcontainer/README.md") + ): + raise AuditFormatError(f"{label}: evidence path is outside the public source allowlist: {path}") def _positive_count(value: object, label: str) -> int: @@ -170,15 +182,21 @@ def _sha256(value: object, label: str) -> str: return value -def _review_reason(value: object, label: str) -> str: +def _review_reason( + value: object, label: str, references: tuple[StructureEvidence | FileEvidence, ...], +) -> str: reason = _nonempty_string(value, label) text = unicodedata.normalize("NFKC", reason).casefold() + for reference in references: + text = text.replace(unicodedata.normalize("NFKC", str(reference.path)).casefold(), " ") text = re.sub(r"(?:https?://|(?:docs|samples)/)\S+", " ", text) text = re.sub( r"\b(?:safety|reason|review(?:ed)?|approval|approved?|preservation|replacement|migration|updated?|revised?|" r"changed?|removed?|deleted?|retained?|preserved?|replaced?|migrated?|" r"needed|necessary|unnecessary|longer|anymore|during|before|after|because|content|document|documentation|" - r"block|material|stuff|latest|current|previous|old|new|" + r"block|material|stuff|latest|current|previous|old|new|evidence|references?|" + r"obsolete|irrelevant|therefore|nothing|significant|suitable|equivalent|" + r"improved?|presentation|has|have|" r"a|an|the|and|or|as|at|in|on|to|of|for|from|with|by|" r"this|that|these|those|it|its|is|was|are|were|be|been|not|no|now)\b", " ", text, @@ -195,6 +213,15 @@ def _review_reason(value: object, label: str) -> str: raise AuditFormatError( f"{label} must describe concrete replacement/preservation details, not generic-only approval phrases" ) + if not any( + re.search( + r"(? StructureEvidence | FileEv } raw = _fields(value, fields, label) path = _relative_path(raw["path"], f"{label}.path") + validate_public_evidence_path(path, f"{label}.path") if kind == "file": return FileEvidence(path, _sha256(raw["sha256"], f"{label}.sha256")) category = raw["category"] - if category not in STRUCTURE_CATEGORIES: + if category not in EVIDENCE_CATEGORIES: raise AuditFormatError(f"{label}.category is not a known structure category") return StructureEvidence( path, category, _sha256(raw["fingerprint"], f"{label}.fingerprint"), @@ -239,14 +267,18 @@ def validate_reviewed_changes( _sha256(fingerprint, f"{label}.{category}.fingerprint") entry_label = f"{label}.{category}.{fingerprint}.approval" if isinstance(approval, ReviewedChange): + change = approval approval = { - "missing_count": approval.missing_count, - "reason": approval.reason, - "evidence": approval.evidence, + "missing_count": change.missing_count, + "reason": change.reason, + "evidence": change.evidence, } - raw = _fields(approval, {"missing_count", "reason", "evidence"}, entry_label) + if change.link_change is not None: + approval["link_change"] = change.link_change + raw = _fields( + approval, {"missing_count", "reason", "evidence"}, entry_label, {"link_change"}, + ) count = _positive_count(raw["missing_count"], f"{entry_label}.missing_count") - reason = _review_reason(raw["reason"], f"{entry_label}.reason") if not isinstance(raw["evidence"], (list, tuple)) or not raw["evidence"]: raise AuditFormatError(f"{entry_label}.evidence must be a non-empty sequence") references = [] @@ -260,7 +292,32 @@ def validate_reviewed_changes( raise AuditFormatError(f"{entry_label}: duplicate evidence reference: {reference.path}") seen.add(key) references.append(reference) - parsed[category][fingerprint] = ReviewedChange(count, reason, tuple(references)) + references = tuple(references) + reason = _review_reason(raw["reason"], f"{entry_label}.reason", references) + link_change = raw.get("link_change") + if "link_change" in raw and category != "local_link_labels": + raise AuditFormatError(f"{entry_label}.link_change is only valid for local_link_labels") + if category == "local_link_labels": + link_change = raw.get("link_change", "replacement") + if link_change not in ("replacement", "redundant-self-link"): + raise AuditFormatError(f"{entry_label}.link_change is unknown") + evidence_categories = { + ref.category for ref in references if isinstance(ref, StructureEvidence) + } + if link_change == "replacement": + if "links" not in evidence_categories: + raise AuditFormatError(f"{entry_label}: replacement approvals require exact links evidence") + else: + if not {"title", "headings"} <= evidence_categories or "links" in evidence_categories: + raise AuditFormatError( + f"{entry_label}: redundant self-link removal requires document identity/topic " + "structure, not replacement links" + ) + if not re.search(r"\bself[- ]link\b|자기.*링크|자체.*링크", reason, re.IGNORECASE) or not re.search( + r"\b(?:remov\w*|omit\w*|drop\w*)\b|삭제|제거|생략", reason, re.IGNORECASE, + ): + raise AuditFormatError(f"{entry_label}.reason must specifically explain self-link removal") + parsed[category][fingerprint] = ReviewedChange(count, reason, references, link_change) return parsed @@ -274,8 +331,8 @@ def load_inventory(path: Path) -> PrePagesInventory: {"version", "baseline_commit", "pages_commit", "rename_similarity", "documents", "dispositions"}, str(path), ) - if type(data["version"]) is not int or data["version"] != 2: - raise AuditFormatError("version must be 2 (evidence-bound content approvals)") + if type(data["version"]) is not int or data["version"] != 3: + raise AuditFormatError("version must be 3 (exact links and public HEAD-tracked evidence)") baseline = _commit_hash(data["baseline_commit"], "baseline_commit") pages = _commit_hash(data["pages_commit"], "pages_commit") similarity = data["rename_similarity"] @@ -373,6 +430,51 @@ def git_text(repo_root: Path, commit: str, path: PurePosixPath) -> str: return git_bytes(repo_root, commit, path).decode("utf-8") +def resolve_public_evidence_files( + repo_root: Path, paths: tuple[PurePosixPath, ...], +) -> Mapping[PurePosixPath, Path]: + """Authorize HEAD-tracked public paths, but return their live working-tree files.""" + root = repo_root.resolve() + tracked = { + PurePosixPath(path) + for path in _git(root, "ls-tree", "-r", "--name-only", "-z", "HEAD").decode("utf-8").split("\0") + if path + } + resolved = {} + to_check = set() + for reference in paths: + validate_public_evidence_path(reference, "evidence") + if reference not in tracked: + raise AuditFormatError(f"evidence path is not tracked at HEAD: {reference}") + try: + current = (root / reference).resolve(strict=True) + if not current.is_relative_to(root): + raise AuditFormatError(f"evidence path is outside repository: {reference}") + target = PurePosixPath(current.relative_to(root).as_posix()) + validate_public_evidence_path(target, f"evidence target for {reference}") + if target not in tracked: + raise AuditFormatError(f"resolved evidence target is not tracked at HEAD: {target}") + if not current.is_file(): + raise AuditFormatError(f"evidence path is not a file: {reference}") + except OSError as error: + raise AuditFormatError(f"cannot resolve current evidence {reference}: {error}") from error + resolved[reference] = current + to_check.update((reference, target)) + if to_check: + result = subprocess.run( + ["git", "-C", str(root), "check-ignore", "--no-index", "-z", "--stdin"], + input="".join(f"{path}\0" for path in sorted(to_check)).encode("utf-8"), + capture_output=True, + check=False, + ) + if result.returncode not in (0, 1): + raise AuditFormatError(f"cannot check ignored evidence paths: {result.stderr.decode('utf-8', errors='replace')}") + ignored = [path for path in result.stdout.decode("utf-8").split("\0") if path] + if ignored: + raise AuditFormatError("ignored evidence paths are ineligible: " + ", ".join(ignored)) + return MappingProxyType(resolved) + + def baseline_paths(repo_root: Path, inventory: PrePagesInventory) -> tuple[PurePosixPath, ...]: _require_full_history(repo_root, inventory.baseline_commit, inventory.pages_commit) _require_commit(repo_root, inventory.baseline_commit) diff --git a/scripts/docs/pre_pages_content.py b/scripts/docs/pre_pages_content.py index c45a6ba0..c9aa72f1 100644 --- a/scripts/docs/pre_pages_content.py +++ b/scripts/docs/pre_pages_content.py @@ -9,6 +9,7 @@ from html import unescape import json from pathlib import Path, PurePosixPath +import posixpath import re from typing import Mapping import unicodedata @@ -20,8 +21,10 @@ PrePagesInventory, ReviewedChange, STRUCTURE_CATEGORIES, + StructureEvidence, git_text, resolve_current_documents, + resolve_public_evidence_files, validate_reviewed_changes, ) @@ -35,6 +38,7 @@ class MarkdownStructure: tables: tuple[str, ...] images: tuple[str, ...] local_link_labels: tuple[str, ...] + links: tuple[str, ...] = () @dataclass(frozen=True) @@ -64,6 +68,10 @@ class DocumentPreservation: _REFERENCE = re.compile(r"(!?)\[([^\[\]]+)\](?:\[([^\[\]]*)\])?") _DEFINITION = re.compile(r"(?m)^ {0,3}\[([^\]]+)\]:\s*" + _DESTINATION + r"[^\n]*$") _ATTRIBUTE = re.compile(r'''([\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))''') +_HTML_LINK = re.compile( + r"""])*)>(.*?)""", + re.IGNORECASE | re.DOTALL, +) _WRAPPER = re.compile( r"]*>", re.IGNORECASE, @@ -74,6 +82,11 @@ def _space(text: str) -> str: return " ".join(text.split()) +def _escaped_match(match: re.Match) -> bool: + prefix = match.string[:match.start()] + return (len(prefix) - len(prefix.rstrip("\\"))) % 2 == 1 + + def _unquote(line: str, depth: int) -> str | None: for _ in range(depth): prefix = re.match(r"^[ \t]{0,3}>[ \t]?", line) @@ -180,6 +193,7 @@ def extract_markdown_structure(text: str) -> MarkdownStructure: tables: list[str] = [] images: list[str] = [] local_link_labels: list[str] = [] + links: list[str] = [] def visible(value: str) -> str: value = _WRAPPER.sub(" ", unescape(value)) @@ -195,13 +209,15 @@ def link(label: str, target: str, image: bool) -> str: if image: images.append(f"{label}\n{PurePosixPath(unquote(urlsplit(target).path)).name}") return "" + if label: + links.append(f"{label}\n{target}") if label and not re.match(r"^(?:[a-z][\w+.-]*:|//)", target, re.IGNORECASE): local_link_labels.append(label) return label def inline(value: str) -> str: literals: dict[str, str] = {} - image_start, label_start = len(images), len(local_link_labels) + image_start, label_start, link_start = len(images), len(local_link_labels), len(links) def protect(match: re.Match) -> str: token = f"\ue000{len(literals)}\ue001" @@ -215,26 +231,54 @@ def restore(text: str) -> str: value = re.sub(r"(`+)(.*?)\1", protect, value) - def html_image(match: re.Match) -> str: - attributes = { + def attributes(raw: str) -> dict[str, str]: + return { name.lower(): double or single or bare - for name, double, single, bare in _ATTRIBUTE.findall(match[0]) + for name, double, single, bare in _ATTRIBUTE.findall(raw) } - return link(attributes.get("alt", ""), attributes.get("src", ""), True) + + def html_image(match: re.Match) -> str: + attrs = attributes(match[0]) + return link(attrs.get("alt", ""), attrs.get("src", ""), True) + + def html_link(match: re.Match) -> str: + if _escaped_match(match): + return match[0] + attrs = attributes(match[1]) + label = link(match[2], attrs["href"], False) if "href" in attrs else match[2] + return f" {label} " value = re.sub(r"]*>", html_image, value, flags=re.IGNORECASE) value = _IMAGE.sub(lambda match: link(match[1], match[2], True), value) - value = _LINK.sub(lambda match: link(match[2], match[3], bool(match[1])), value) + value = _HTML_LINK.sub(html_link, value) + + def markdown_link(match: re.Match) -> str: + if _escaped_match(match): + return match[0] + return link(match[2], match[3], bool(match[1])) + + value = _LINK.sub(markdown_link, value) def reference(match: re.Match) -> str: + if _escaped_match(match): + return match[0] key = _space(match[3] or match[2]).casefold() if key not in definitions: return match[0] return link(match[2], definitions[key], bool(match[1])) - result = visible(_REFERENCE.sub(reference, value)) + value = _REFERENCE.sub(reference, value) + + def autolink(match: re.Match) -> str: + if not _escaped_match(match): + link(match[1], match[1], False) + return match[0] + + value = re.sub(r"<((?:https?://|mailto:)[^<>\s]+)>", autolink, value, flags=re.IGNORECASE) + result = visible(value) images[image_start:] = [restore(image) for image in images[image_start:]] local_link_labels[label_start:] = [restore(label) for label in local_link_labels[label_start:]] + links[link_start:] = [restore(value) for value in links[link_start:]] return restore(result) def heading(value: str, level: int) -> None: @@ -251,7 +295,9 @@ def heading(value: str, level: int) -> None: code.append(f"{language}\n{body}") continue body = re.sub( - r"(?m)^[ \t]*" + _WRAPPER.pattern + r"[ \t]*$", "", _DEFINITION.sub("", body), + r"(?m)^[ \t]*" + _WRAPPER.pattern + r"[ \t]*$", + lambda match: match[0] if re.match(r"\s* None: return MarkdownStructure( title, tuple(headings), tuple(prose), tuple(code), tuple(tables), tuple(images), tuple(local_link_labels), + tuple(links), ) @@ -320,7 +367,10 @@ def _verify_current_evidence( baseline_path: PurePosixPath, reviewed_changes: Mapping[str, Mapping[str, ReviewedChange]], ) -> None: - root = repo_root.resolve() + files = resolve_public_evidence_files(repo_root, tuple({ + ref.path for group in reviewed_changes.values() for approval in group.values() + for ref in approval.evidence + })) contents: dict[PurePosixPath, bytes] = {} structures: dict[PurePosixPath, MarkdownStructure] = {} for category, approvals in reviewed_changes.items(): @@ -329,12 +379,7 @@ def _verify_current_evidence( label = f"{baseline_path}.{category}.{fingerprint}.evidence[{index}] ({reference.path})" if reference.path not in contents: try: - path = (root / reference.path).resolve(strict=True) - if not path.is_relative_to(root): - raise AuditFormatError(f"{label}: evidence path is outside repository") - if not path.is_file(): - raise AuditFormatError(f"{label}: evidence path is not a file") - contents[reference.path] = path.read_bytes() + contents[reference.path] = files[reference.path].read_bytes() except OSError as error: raise AuditFormatError(f"{label}: cannot read current evidence: {error}") from error data = contents[reference.path] @@ -360,6 +405,40 @@ def _verify_current_evidence( ) +def _verify_self_link_removal( + baseline_path: PurePosixPath, + current_path: PurePosixPath, + baseline: MarkdownStructure, + current: MarkdownStructure, + fingerprint: str, + approval: ReviewedChange, +) -> None: + labels = { + label for label in baseline.local_link_labels + if sha256(f"local_link_labels\0{label}".encode("utf-8")).hexdigest() == fingerprint + } + links = [value.partition("\n")[2] for value in baseline.links if value.partition("\n")[0] in labels] + if len(links) < approval.missing_count: + raise AuditFormatError(f"{baseline_path}: self-link removal has no matching baseline link evidence") + for target in links: + url = urlsplit(target) + if ( + url.scheme or url.netloc or url.query or url.fragment or not url.path or url.path.startswith("/") + or PurePosixPath(posixpath.normpath(str(baseline_path.parent / unquote(url.path)))) != baseline_path + ): + raise AuditFormatError(f"{baseline_path}: removal is not a redundant document self-link") + identities = [ + ref for ref in approval.evidence + if isinstance(ref, StructureEvidence) and ref.path == current_path + and ref.fingerprint in { + sha256(f"{ref.category}\0{value}".encode("utf-8")).hexdigest() + for value in _values(current, ref.category) + } + ] + if not {"title", "headings"} <= {ref.category for ref in identities}: + raise AuditFormatError(f"{baseline_path}: self-link removal must bind current document identity/topic structure") + + def compare_markdown( baseline_path: PurePosixPath, current_path: PurePosixPath, @@ -395,6 +474,10 @@ def compare_markdown( f"{baseline_path}.{category}.{fingerprint}: missing_count changed: " f"approved {approval.missing_count}, found {actual}" ) + if approval.link_change == "redundant-self-link": + _verify_self_link_removal( + baseline_path, current_path, baseline, current, fingerprint, approval, + ) approved.add((category, fingerprint)) if approved: if repo_root is None: diff --git a/scripts/docs/pre_pages_inventory.yml b/scripts/docs/pre_pages_inventory.yml index 9203d816..ff91941c 100644 --- a/scripts/docs/pre_pages_inventory.yml +++ b/scripts/docs/pre_pages_inventory.yml @@ -1,5 +1,5 @@ -# Reviewed evidence bindings: exact baseline loss counts and current structure/file proofs. -version: 2 +# Reviewed exact links and public HEAD-tracked evidence; no snapshot or prose-only link approvals. +version: 3 baseline_commit: a4e680116db1016a698e64baae9efde858a8eafa pages_commit: 9ace966742ff92a00fc84bcc21529af47b8661af rename_similarity: 20 @@ -168,8 +168,8 @@ documents: evidence: - kind: structure path: docs/services/azure-ai-search/custom-vectorization/index.md - category: prose - fingerprint: 2fe6e3de7e64b5c6c52dbe7075d92314a69ff7768dd08a30484f467518d4648b + category: links + fingerprint: 9b0ace50c6b1c47e461d47ee6dbc75ae03f992f4343be4bcf2dbdbedb129d83a count: 1 - kind: file path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter/Dockerfile @@ -183,6 +183,7 @@ documents: - kind: file path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter/requirements.txt sha256: 468be2c9de7dbe4976f66a16981d181fe746622ec5f7c6d1e08fd3c9d5a082df + link_change: replacement - baseline_path: aisearch/custom_vectorization/02_custom_vectorization.md pages_path: guides/azure-ai-search/custom-web-api-vectorization/index.md reviewed_changes: @@ -495,8 +496,8 @@ documents: evidence: - kind: structure path: docs/services/azure-ai-search/custom-vectorization/a10-vs-t4/index.md - category: prose - fingerprint: 97338df5886d61b782ea49a09180fd4552f7074d7069018d74042baf98da0daf + category: links + fingerprint: e7c58296e9097c223f9eb6050d1f5889e656318e55e8074ee28045fdf9f5ef6d count: 1 - kind: file path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/Dockerfile @@ -525,6 +526,7 @@ documents: - kind: file path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/requirements.txt sha256: 25498c398913549d3a8ed10b9c105067fdfe0a6f95709e15ad44b90cf752773f + link_change: replacement c1168e03ae9ae43f12052ccdcab60029b049f217eea89f5193f9471bda029f51: missing_count: 1 reason: Retained label 'bench_qps_sweep.py' in docs/services/azure-ai-search/custom-vectorization/a10-vs-t4/index.md, @@ -534,12 +536,13 @@ documents: evidence: - kind: structure path: docs/services/azure-ai-search/custom-vectorization/a10-vs-t4/index.md - category: prose - fingerprint: d00cfbfb081a8504466dd94a869d91b669a549ef3c5ffb63f30b2a771a7d3851 + category: links + fingerprint: c850fced94c8ff5aeabbbcee0e1e22ddb8ce8119a2f16def4345eb8a692baeea count: 1 - kind: file path: docs/services/azure-ai-search/custom-vectorization/samples/implementation/bench/bench_qps_sweep.py sha256: db9e561cb63b95891dc1c55e0db55d86d4f3c536cbaedcaf9fadb0075acd23c3 + link_change: replacement - baseline_path: aisearch/custom_vectorization/ref_bge_m3_vs_qwen3_comparison.md pages_path: research/azure-ai-search/bge-m3-vs-qwen3-embedding/index.md reviewed_changes: @@ -596,14 +599,15 @@ documents: evidence: - kind: structure path: docs/services/azure-ai-search/custom-vectorization/rag-chunking/index.md - category: local_link_labels - fingerprint: 3532fe14417fc9b274202e0d46b30b6b219ade749affaf6fd42a0eda878352c1 + category: links + fingerprint: 56cee62b75ddd0a0342108e896b9393b27f937b8a0cced3484e5e05a9b28c8bc count: 1 - kind: structure path: docs/services/azure-ai-search/custom-vectorization/custom-web-api/index.md category: title fingerprint: d2db99b98bfff9cde68bcb7933e337db05a9bcdc15635c51d0bfeb63ad756e1e count: 1 + link_change: replacement - baseline_path: aisearch/eventual_consistency_reindex.md pages_path: cases/azure-ai-search/eventual-consistency-reindex/index.md reviewed_changes: @@ -617,12 +621,13 @@ documents: evidence: - kind: structure path: docs/services/azure-ai-search/eventual-consistency-reindex/index.md - category: prose - fingerprint: f04da0933ebd55bf75a340f7123745b4e9ee85c4d4817fbff8bf720970a90df2 + category: links + fingerprint: 8f9b2007f57c9817d967e5e8ee684069bf631121b3c1db26d50354a05a5fb1a2 count: 1 - kind: file path: docs/services/azure-ai-search/eventual-consistency-reindex/samples/reproduction/repro.py sha256: 2184fc28668b5484826a64aa8a3c4d70d11b449327ac207f0068bd17ab179db8 + link_change: replacement - baseline_path: aks/affinity.md pages_path: guides/azure-kubernetes-service/pod-affinity-distribution/index.md reviewed_changes: {} @@ -697,14 +702,15 @@ documents: evidence: - kind: structure path: docs/services/azure-kubernetes-service/netapp-files-cpu-io-wait/index.md - category: local_link_labels - fingerprint: 88b31595c8f8f7af56271e3cc5e2673e491406aca223c746104bd9d484a9f7f8 + category: links + fingerprint: a628138f90ce1391b9bc62428093bf41f905f3eee7a753bac817dd2f026e01b9 count: 1 - kind: structure path: docs/services/azure-kubernetes-service/file-io-throttling/index.md category: title fingerprint: e9930620dc993b0893956c55fdcd6b18d4f5eaf134f26cf22ea8089309403f49 count: 1 + link_change: replacement - baseline_path: aks/kaito_opensource_model.md pages_path: guides/azure-kubernetes-service/kaito-open-source-model/index.md reviewed_changes: {} @@ -855,8 +861,8 @@ documents: evidence: - kind: structure path: docs/services/azure-kubernetes-service/remote-cluster-local-development/index.md - category: prose - fingerprint: 9f3757040e16c6a5b9108dbd776c4e33aaea5902b91fe0455ef283d212bc0008 + category: links + fingerprint: 46d807c00926982c7b8672281bf38872483fcc6c9df95a8a7a064efa54c81f19 count: 1 - kind: file path: docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/README.md @@ -873,6 +879,7 @@ documents: - kind: file path: docs/services/azure-kubernetes-service/remote-cluster-local-development/samples/local-development-loop/sample.yml sha256: 35a24d4a46f99e284940dd9b6efd777d1f548e826be9f199c6795c42ddc7bfe8 + link_change: replacement - baseline_path: aks/memray_leak_profiling.md pages_path: guides/azure-kubernetes-service/python-memory-leak-memray/index.md reviewed_changes: @@ -916,12 +923,13 @@ documents: evidence: - kind: structure path: docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md - category: prose - fingerprint: e1c0a8ceed18b064a4cd6c41e82d44d946ed1cac96a8dc21675c6409472d2f47 + category: links + fingerprint: fee33260d2511b1b2d2e168c7168d47611abfed0c8910689283b88b2a2333c88 count: 1 - kind: file path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/debug/Dockerfile sha256: afd72d820f40468697e146cfba9793872d878ee11b5d0e271f6ff1d0ae248044 + link_change: replacement 88ce896b3271d19d7d900463556c60803f17ed606892a6fa76187d5b4f4c564b: missing_count: 1 reason: Retained label 'reports/leak-leaks.html' in docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md, @@ -931,12 +939,13 @@ documents: evidence: - kind: structure path: docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md - category: prose - fingerprint: 479770d6671f82490cd06fbac4a40702d63cc80cee2be10b0a71e1d217872145 + category: links + fingerprint: e929e633981628fd6cb77bbe2aa1f14689ad0ab2569b98f79ae9fe74777e4cb1 count: 1 - kind: file path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-leaks.html sha256: 6ee1a470eaac5a37c53eb2ac8c13d294d1f0eacc058ba995c15f59a10cb3d89a + link_change: replacement f7e6c7e8504b5d8b79baedd326e64c9dd09a3884626d55b4286a83071b6a0f64: missing_count: 1 reason: Retained label 'reports/leak-flamegraph.html' in docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md, @@ -946,12 +955,13 @@ documents: evidence: - kind: structure path: docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md - category: prose - fingerprint: 479770d6671f82490cd06fbac4a40702d63cc80cee2be10b0a71e1d217872145 + category: links + fingerprint: 0002a43efaf77a16c5fdd98268e37cefe0cd78e826ca92ababa4437b869e03d5 count: 1 - kind: file path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/reports/leak-flamegraph.html sha256: 989ab827d87d5879ad38013bbc504a2280475b6c1aaadbe95b521b8e19dcbfc5 + link_change: replacement cce3bf06ad38a4d47f1debc2804da8da45849161ea38791f51ffb6227c6ae4ca: missing_count: 1 reason: Retained label 'memray-leak-profiling/' in docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md, @@ -961,8 +971,8 @@ documents: evidence: - kind: structure path: docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md - category: prose - fingerprint: 39bd9fd637f7d4fb8667658fc6cbc504a5378ed0e465cb507c70e390edca5cd1 + category: links + fingerprint: b2db1648ea413fa4b1e8eb1eae03a9b1a09c5caa782c182874658d62cdc147ff count: 1 - kind: file path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/Dockerfile @@ -1000,6 +1010,7 @@ documents: - kind: file path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/sample.yml sha256: 0d354f8901fdd8b0830902a135fc3ebbcda9d41eebf595530eee92845f103d85 + link_change: replacement 64c7be708c8f93ffae9396110852cc8d88ffff643ac70c496405c59ca711debd: missing_count: 1 reason: Retained label 'Dockerfile' in docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md, @@ -1009,12 +1020,13 @@ documents: evidence: - kind: structure path: docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md - category: prose - fingerprint: 3c5b71ff10a8905c165bac5674b8941cb2dd18d60de275a24b1f1e0d1bf12dc5 + category: links + fingerprint: 21b46b9ea9d0c93e23f756d199f30a1c111c1e44536fb0f83665d7a279e939ed count: 1 - kind: file path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/Dockerfile sha256: a6f157d5d5d979095c9f0404c2f8eb7dcc4acf011092b56a9473c0d9e8df48cd + link_change: replacement - baseline_path: aks/pod_db_query_latency.md pages_path: cases/azure-kubernetes-service/pod-database-query-latency/index.md reviewed_changes: {} @@ -1031,8 +1043,8 @@ documents: evidence: - kind: structure path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md - category: prose - fingerprint: 1006bf65943d422efb8b73029add1227cef308f196ac74d6770a3406237fc6d9 + category: links + fingerprint: 5191d2b491bb4f5544e0b06837f8e4f6a9a9a300737c0b7e629aa7b8406ae81c count: 1 - kind: file path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/Dockerfile @@ -1073,6 +1085,7 @@ documents: - kind: file path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/test.py sha256: 1b7ec68ad9cc9329b6241d5b00eb679cc43b70902e13f23b2f4eb301c20c51a2 + link_change: replacement d66ec5f5968445f509a55315423ed577736ca1d1d54c36b44c406ae0203af3f1: missing_count: 1 reason: Retained label 'app.py' in docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md, @@ -1082,12 +1095,13 @@ documents: evidence: - kind: structure path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md - category: prose - fingerprint: 1006bf65943d422efb8b73029add1227cef308f196ac74d6770a3406237fc6d9 + category: links + fingerprint: 36f7ac601fd6f39eade110008745493a617e9e01cfb7f57cf888a57183adbfb7 count: 1 - kind: file path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/app.py sha256: acaeb43adfc0dbe20c0a0ed22c4a5fd3d5dd7a5e80a3dee3ba552de83236217b + link_change: replacement b974b045198c87e2e93578433fe9a3c2b3c6c76147ba84c204061456b504a306: missing_count: 1 reason: Retained label 'deployment.yaml' in docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md, @@ -1097,12 +1111,13 @@ documents: evidence: - kind: structure path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md - category: prose - fingerprint: 1006bf65943d422efb8b73029add1227cef308f196ac74d6770a3406237fc6d9 + category: links + fingerprint: 9ac81652508bbe1827b6516b029fa7d3d7bbf7c9ec809f7a779b8adc6debae43 count: 1 - kind: file path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/deployment.yaml sha256: a185097a2eebb6d50dc1cd04cd43a1ba689002302e45e78b8c0df243d93fe27e + link_change: replacement a0d74fb7f5bca6aea72cd6899b42ce77135fa30bd556e326b57fbcda776ddd71: missing_count: 1 reason: Retained label 'deploy.sh' in docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md, @@ -1112,12 +1127,13 @@ documents: evidence: - kind: structure path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md - category: prose - fingerprint: 1006bf65943d422efb8b73029add1227cef308f196ac74d6770a3406237fc6d9 + category: links + fingerprint: 2ff59cf9791a4e30802bf76fb870aca744167a9f04af8c6c984524b4f77eb138 count: 1 - kind: file path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/deploy.sh sha256: 95c380cec713351f9b12d69a74a074b0ccd6d9750f3bbb7092461d8482717748 + link_change: replacement c2af7f80d42e50d1a7508f93267aa137e51fd3d85f2de2798fe712755a74fb88: missing_count: 1 reason: Retained label 'test.py' in docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md, @@ -1127,12 +1143,13 @@ documents: evidence: - kind: structure path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/index.md - category: prose - fingerprint: 1006bf65943d422efb8b73029add1227cef308f196ac74d6770a3406237fc6d9 + category: links + fingerprint: 0cdbc47a3664256f9b6d771f88c3b967706591ba7d0d270b963cbefad18ad0f0 count: 1 - kind: file path: docs/services/azure-kubernetes-service/pyroscope-anf-s3/samples/pyroscope-chat-profiling/test.py sha256: 1b7ec68ad9cc9329b6241d5b00eb679cc43b70902e13f23b2f4eb301c20c51a2 + link_change: replacement - baseline_path: aks/spot_gpu_kaito_llm.md pages_path: guides/azure-kubernetes-service/spot-h100-kaito/index.md reviewed_changes: {} @@ -1209,24 +1226,10 @@ documents: evidence: - kind: structure path: docs/services/azure-kubernetes-service/workload-identity-databricks/index.md - category: prose - fingerprint: 61960f199bc73556fadf8ac0b3b6592938d9c1ccdd798a5593f94859635bd6f6 - count: 1 - - kind: structure - path: docs/services/azure-kubernetes-service/workload-identity-databricks/index.md - category: prose - fingerprint: e531423c958685a650322e2013634bf61c5ebe6093979893aa53144e4b33f7b7 - count: 1 - - kind: structure - path: docs/services/azure-kubernetes-service/workload-identity-databricks/index.md - category: prose - fingerprint: 02a34c9f830a85fe5b8579f1004564f5300f50fbb8c593b67226b91b26b8546e - count: 1 - - kind: structure - path: docs/services/azure-kubernetes-service/workload-identity-databricks/index.md - category: prose - fingerprint: 6923c4ffca0b935761c8494001642f0738154b6642aa614c72f2c21018110b07 - count: 1 + category: links + fingerprint: 86f50627741859b440339d249723d7025a84740b470e8123c9da64d9f09a01e7 + count: 4 + link_change: replacement - baseline_path: appgw/sse_response_buffer_disable.md pages_path: guides/azure-application-gateway/sse-response-buffering/index.md reviewed_changes: {} @@ -1317,12 +1320,13 @@ documents: evidence: - kind: structure path: docs/services/azure-storage/mobile-resumable-upload-tus/index.md - category: prose - fingerprint: a6aa91987c3f30969d2b3494e1b2b1d3f30a30fcde4b229885a03a38705a9a46 + category: links + fingerprint: de70232d0400c68a5b691ea4172d24e35b33a9df42b4e0e8661731e5c9cabd4b count: 1 - kind: file path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/architecture.excalidraw sha256: 44032168d2c735c4472cdb05d28b15de2d8d0577cf8c40b86ef645eb3db83ff2 + link_change: replacement 8029079ca642dbd6a81ba1d4c010793d347d31af7b12942eef74073216953c8f: missing_count: 1 reason: Replaced retired path label with 'spring-application/' in docs/services/azure-storage/mobile-resumable-upload-tus/index.md, @@ -1332,8 +1336,8 @@ documents: evidence: - kind: structure path: docs/services/azure-storage/mobile-resumable-upload-tus/index.md - category: prose - fingerprint: daa04b206085e46153c3f3898b9846607cd3b0c40013491c3f8c930587067040 + category: links + fingerprint: c56b8f108c3c3f95c4d9972de881ce0e8ee4ce889c676106951429bde3864131 count: 1 - kind: file path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/.gitignore @@ -1380,6 +1384,7 @@ documents: - kind: file path: docs/services/azure-storage/mobile-resumable-upload-tus/samples/spring-application/src/main/resources/application.yml sha256: dd03e866d03037a3e0da5a4dd75768bc8ff669cfd80e4ec10b1d8a38867ea318 + link_change: replacement - baseline_path: cosmosdb/nodejs/client.md pages_path: guides/azure-cosmos-db/nodejs-client-optimization/index.md reviewed_changes: {} @@ -1492,14 +1497,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md - category: local_link_labels - fingerprint: a4bddffc5fe27ac29dee1db3247764f0bcd4add39b2363df11e3c32386124149 + category: links + fingerprint: 8ddca1e54dfe1f211035522e92a5f2839935742a05216e81de1573fb6ab01207 count: 1 - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md category: title fingerprint: 6b2f2e2b757771a85135aab52ed8f6f23cb7c1f61c01a4792c1b57fd5edd6ad0 count: 1 + link_change: replacement 1a367b94c351d88a8178eab696e123992e8ddec22279845c41be4c9c8d8ba89d: missing_count: 1 reason: Replaced retired path label with 'Prometheus + Grafana 대시보드 구성' in docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md, @@ -1509,14 +1515,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/catch-up-benchmark/index.md - category: local_link_labels - fingerprint: 86e83c386fa53d20ab065fd3469fd60fbbad27dbca3e7cefc13615610e567543 + category: links + fingerprint: 1e82b299fa84f13be524a392050e4c39ceb63a3c555ec16989c0103f4649f913 count: 1 - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md category: title fingerprint: 8a3b50c0fb8014d577ca4609aeb5e8d1d7b0c2b6b3892893a5124296ca8f5b5b count: 1 + link_change: replacement - baseline_path: loadtest/loadtest_locust_appgw_aks.md pages_path: guides/azure-load-testing/locust-appgw-aks-private/index.md reviewed_changes: {} @@ -1691,8 +1698,8 @@ documents: evidence: - kind: structure path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md - category: local_link_labels - fingerprint: 6e0523469bc57a9e724a9b332a11bec54fd18dfaebc9b1d7609bef38f3421e6a + category: links + fingerprint: 80be23731d6f5106ae82a486c59fa81af54a9754492ddf0d5de0c3602c10d988 count: 1 - kind: structure path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md @@ -1704,6 +1711,7 @@ documents: category: prose fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 count: 1 + link_change: replacement 8d4cbf9cdc1e35d919f0900882f792fa23f92052eac2647f749a6d41c54e9045: missing_count: 1 reason: Restored label '02. 아키텍처 패턴' in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md @@ -1713,8 +1721,8 @@ documents: evidence: - kind: structure path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md - category: local_link_labels - fingerprint: 8d4cbf9cdc1e35d919f0900882f792fa23f92052eac2647f749a6d41c54e9045 + category: links + fingerprint: a30091cbd5bcfc7d0483cff3569a326722afe60740f3b0d44a24164543d62522 count: 1 - kind: structure path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md @@ -1726,6 +1734,7 @@ documents: category: prose fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 count: 1 + link_change: replacement ac95bcf8bf57be7eb83236b143401db16a4a998d6b5e689ad646a8774e57f5c3: missing_count: 1 reason: Restored label '04. 프레임워크 비교' in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md @@ -1735,8 +1744,8 @@ documents: evidence: - kind: structure path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md - category: local_link_labels - fingerprint: ac95bcf8bf57be7eb83236b143401db16a4a998d6b5e689ad646a8774e57f5c3 + category: links + fingerprint: 5a32bb677cf4631b3c9d6db4bc0aa2ad78db73af574523e83731eff823d975f0 count: 1 - kind: structure path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md @@ -1748,6 +1757,7 @@ documents: category: prose fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 count: 1 + link_change: replacement 10d1e473e9ebe8c05b3c51bb0150eb66e3c7f8e4318b549c32577c2d208c4099: missing_count: 1 reason: Restored label '05. 프로덕션과 평가' in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md @@ -1757,8 +1767,8 @@ documents: evidence: - kind: structure path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md - category: local_link_labels - fingerprint: 10d1e473e9ebe8c05b3c51bb0150eb66e3c7f8e4318b549c32577c2d208c4099 + category: links + fingerprint: 8fb0a588984a83f0caec2b9edf9f597731d6e3ea793bfc90311158349a91417f count: 1 - kind: structure path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md @@ -1770,6 +1780,7 @@ documents: category: prose fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 count: 1 + link_change: replacement ee708ebb80e2486837892f2629a6ab654c815a44e5216e406b5d9894e1044571: missing_count: 1 reason: Retained label 'images/01-memory-taxonomy.svg' in docs/services/microsoft-foundry/agent-memory/index.md, @@ -1779,12 +1790,13 @@ documents: evidence: - kind: structure path: docs/services/microsoft-foundry/agent-memory/index.md - category: tables - fingerprint: a9100cd68afb54137c8011ff760922aef7458c8fbf8cadb40e8a072ce9bec391 + category: links + fingerprint: fa308a97bc47d4f5fdd91454ca04e978d5ddac8059a2f343e153e7cc2985e90b count: 1 - kind: file path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/01-memory-taxonomy.svg sha256: 05597176b2cddfae85b1db975a76d9a55b294256e08cbad2cd3041557494369c + link_change: replacement 11a1f30b2750cc2cacdf6a469f4db322fba63e0a01c49f097bb0003a0b7777ee: missing_count: 1 reason: Retained label 'images/02-architecture-patterns.svg' in docs/services/microsoft-foundry/agent-memory/index.md, @@ -1794,12 +1806,13 @@ documents: evidence: - kind: structure path: docs/services/microsoft-foundry/agent-memory/index.md - category: tables - fingerprint: 6015cdac73fed89d3668aea50442cffd1138f327dded478b978f56063729ab85 + category: links + fingerprint: 90ce78c8160d909ce47656723738b91b67fb1de6e2bcb554c59c6bef2f9667a6 count: 1 - kind: file path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/02-architecture-patterns.svg sha256: 3d956a3c082a022066c0a728ca71cfee06a5836b4000d7684e8675e26c2bce84 + link_change: replacement db83ec5f48f147e309ada3539b4cb341f1eac05606f8e063f97c4b3528e26a5c: missing_count: 1 reason: Retained label 'images/03-memory-pipeline.svg' in docs/services/microsoft-foundry/agent-memory/index.md, @@ -1809,12 +1822,13 @@ documents: evidence: - kind: structure path: docs/services/microsoft-foundry/agent-memory/index.md - category: tables - fingerprint: 8e1328630f60147e6c4f3bb21c1f846ca73a4c7ec8c2d3beffd0918db9a5c670 + category: links + fingerprint: 27ddce3afc1f81877ec8b3bdfa019208701e99f45b5ee924deaa5cc22433c2c0 count: 1 - kind: file path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/03-memory-pipeline.svg sha256: 1588cb6240cbeba2f41b9725783b48fb8f5a1b87f22f1cfd6bc2e4e54631fbb1 + link_change: replacement 9e5f8f51ef96fa84bc4552892a69cdfc89d8f94a5ef1a822ceabeddf02e35464: missing_count: 1 reason: Retained label 'images/04-production-tiers.svg' in docs/services/microsoft-foundry/agent-memory/index.md, @@ -1824,12 +1838,13 @@ documents: evidence: - kind: structure path: docs/services/microsoft-foundry/agent-memory/index.md - category: tables - fingerprint: 9c91bd9ca1db92d41d335562d68a03bcec5cdd113c668ac45d465d55ce4cd706 + category: links + fingerprint: ed33b4ee6186170ae96f4db8ceb5c4d532d929401cec15d45786ee8b2b500ad0 count: 1 - kind: file path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/04-production-tiers.svg sha256: f1e42cc6e3987923e8c849f3228edad7d5cce8b7ccd80d5e091b39f1e3d3aafb + link_change: replacement fe2b3e96ca22941642e539d494b3861481ebfc8abc5a54ec0a1b5705f21b25b9: missing_count: 1 reason: Retained label 'images/05-commerce-blueprint.svg' in docs/services/microsoft-foundry/agent-memory/index.md, @@ -1839,12 +1854,13 @@ documents: evidence: - kind: structure path: docs/services/microsoft-foundry/agent-memory/index.md - category: tables - fingerprint: 0aa6f09bfeab29d6ef473471f26333a1ffa3a34c1bbcfb10fcb4d6e7cf322a2c + category: links + fingerprint: 6967936c08a78ed521c48b58fabe0e4428633a2a9609fcec2093aceb4438b829 count: 1 - kind: file path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/images/05-commerce-blueprint.svg sha256: ad6cda6b2f207d1ac7d5f2ee19bb8e53ee5d292942c29967d14b271a0b372ab6 + link_change: replacement e545fbfd8a6db9a606f96c20118846de2fb6ce95bd45a6a6c9707adb282da640: missing_count: 1 reason: Restored label '06. 커머스 적용 설계' in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md @@ -1854,8 +1870,8 @@ documents: evidence: - kind: structure path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md - category: local_link_labels - fingerprint: e545fbfd8a6db9a606f96c20118846de2fb6ce95bd45a6a6c9707adb282da640 + category: links + fingerprint: 5fc32c1aecb7e3275f4b65cec68318a8dd4b646971359583a75729b5588fe1d3 count: 1 - kind: structure path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md @@ -1867,6 +1883,7 @@ documents: category: prose fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 count: 1 + link_change: replacement a63a178680757909d7b1cb6ad4e67e89420ea1882685f828f240218517493cb2: missing_count: 1 reason: Restored label '03. 파이프라인과 검색' in docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md @@ -1876,8 +1893,8 @@ documents: evidence: - kind: structure path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md - category: local_link_labels - fingerprint: a63a178680757909d7b1cb6ad4e67e89420ea1882685f828f240218517493cb2 + category: links + fingerprint: 6edd451eb506f0b64755775b34f52e3f84b51ddc9d5d3082fbd5325022fa4ded count: 1 - kind: structure path: docs/services/microsoft-foundry/agent-memory/samples/research-artifacts/README.md @@ -1889,6 +1906,7 @@ documents: category: prose fingerprint: 56058f5a83670fabf5c4bea061931702d3a582c44b1ad70c43f9008dd7956a01 count: 1 + link_change: replacement - baseline_path: memory/foundry-gpt-memory-v1.md pages_path: guides/microsoft-foundry/gpt-memory-layer/index.md reviewed_changes: @@ -1936,15 +1954,21 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/index.md - category: prose - fingerprint: 6c43c121382840f8f19937ac23214b0f78528fd1586cf8320fd202c62f28fd04 - count: 2 + category: links + fingerprint: 4d2b5788682307ef4e79f9376844980ab02f473f10c0a6a52b00feebd0887843 + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/index.md + category: links + fingerprint: dac9a181e89f1fc63ca186bd97c8756b502704db3e9889130c78547395bc2e73 + count: 1 - kind: file path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/sre-agent-process.svg sha256: 51f68bd5daecfafaa47702f98c6753775fee7f2deb4e772f65b6d86f161f0095 - kind: file path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/briefing/s1-three-panel.svg sha256: f8ab268f0999ddb6727b62ab999c05f6686d73f9f48d7db471cf9a18ea2a95f3 + link_change: replacement 6acfc255c5e8f3e2260323007f2f377c059f04a4cf27090d45f826a526d4d4f7: missing_count: 1 reason: Retained label 'Issue 본문 보기' in docs/services/azure-monitor/azure-sre-agent/index.md, @@ -1954,12 +1978,13 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/index.md - category: prose - fingerprint: 53ddda52b094ee04b6f81e7437bc88e8c6882150fe44286728e3688c7c91e39a + category: links + fingerprint: 4f915ee74e0c5d92bcfe00bc89efc4ee5f08d86d8235c9584c41d3895181adfa count: 1 - kind: file path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-github-issue.md sha256: 6f25ab7c79a3950ccfbb0ca34a8f69e6e10855c350f940ef6e1286b05b143f1e + link_change: replacement 875bedfdbc15c549d09155e977dc244e9e99297dbf14c1ac75dee7f9ec774840: missing_count: 1 reason: Retained label 'HTML 메일 보기' in docs/services/azure-monitor/azure-sre-agent/index.md, @@ -1969,12 +1994,13 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/index.md - category: prose - fingerprint: 3d9b22355c89174f45308e79e3ac1eaa707a16e1fefe632338dbc844c7fe01d6 + category: links + fingerprint: e81041adc637938c05e5b77bc29187c0e2abd28184f177eeca62fce6c1ad35a5 count: 1 - kind: file path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-incident-summary.html sha256: c025993ddafaacaaa12bc956e20a036ac1046ecd052a3de4aa5ab72549f66b90 + link_change: replacement 6d2ba3851d9f7930b9b842b58ab1c5a5124455736915e1a215b0f3aa3b10101d: missing_count: 1 reason: Retained label 'RFC 5322 메일 파일 보기' in docs/services/azure-monitor/azure-sre-agent/index.md, @@ -1984,12 +2010,13 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/index.md - category: prose - fingerprint: 2223d51a898ea9e198ef287b34459e809b143c354f0a9ee36656d234e5190597 + category: links + fingerprint: b5c7e0ca67c4a53034302976378b025cac46403983a023a96e427c196e1f54e6 count: 1 - kind: file path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/notifications/s1-incident-summary.eml sha256: 625a4cfad60d508c243ef361bc9bafb30391651c23dd9478ec0de98f34d2a2f6 + link_change: replacement - baseline_path: monitor/hdinsight-kafka-monitoring.md pages_path: research/azure-monitor/hdinsight-kafka-monitoring-options/index.md reviewed_changes: @@ -2059,14 +2086,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md - category: local_link_labels - fingerprint: 694ca5df68c8496b993b6657861dd36d3d0d3d95abab7d5cf23ff2f0f587598e + category: links + fingerprint: 334cae235e554d53042ccca07b4651ce1cb8181940ea606511e22a4e17a780dc count: 3 - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md category: title fingerprint: 8a3b50c0fb8014d577ca4609aeb5e8d1d7b0c2b6b3892893a5124296ca8f5b5b count: 1 + link_change: replacement - baseline_path: monitor/hdinsight-kafka-prometheus-grafana.md pages_path: guides/azure-monitor/hdinsight-kafka-prometheus-grafana/index.md reviewed_changes: @@ -2109,14 +2137,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md - category: local_link_labels - fingerprint: a4bddffc5fe27ac29dee1db3247764f0bcd4add39b2363df11e3c32386124149 + category: links + fingerprint: 8ddca1e54dfe1f211035522e92a5f2839935742a05216e81de1573fb6ab01207 count: 1 - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/index.md category: title fingerprint: 6b2f2e2b757771a85135aab52ed8f6f23cb7c1f61c01a4792c1b57fd5edd6ad0 count: 1 + link_change: replacement 4cbc12caed5b287e12ad58a40e0bffb7faca665597ab4ee1c45211467bb82ca2: missing_count: 1 reason: Retained label 'assets/prometheus-grafana/' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, @@ -2126,8 +2155,8 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md - category: prose - fingerprint: 41a658b52dbbc527239649d30f140290f06454456f95dd05ba03666bcd38474a + category: links + fingerprint: 57849b2e19d78098e4c9471a851dbd89cec4e34534366bb8e3ffcc8ab09cafc8 count: 1 - kind: file path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/docker-compose.yml @@ -2153,6 +2182,7 @@ documents: - kind: file path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/prometheus.yml sha256: b32f4f5111347a0f0f897ecb795cdcff69ccb563ceee3080f90a71aeec52bebd + link_change: replacement 8caa2a204eec50db21c591280d846fac7d80f202c6a829dd8b3843700d5f969f: missing_count: 1 reason: Retained label 'jmx/kafka-rules.yml' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, @@ -2162,12 +2192,13 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md - category: prose - fingerprint: d2572892b8f76473f2b077c80e99342fc69d2e420aa2733e2fa1a25e3bb4243b + category: links + fingerprint: 67085c6c9b35338b960ea8da39b354d53a888061b20c61cfdd8efd26f95e0b9e count: 1 - kind: file path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/jmx/kafka-rules.yml sha256: 5e080b0afde650f3c8a619f5da3c1045cfa427bb6250125560158fa8f170370b + link_change: replacement 656c7a584417e11b56b29c13f30fb5ca9620bb81643a4e4ba550f196afd4e658: missing_count: 1 reason: Retained label 'jmx/b0.yml.example' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, @@ -2177,12 +2208,13 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md - category: prose - fingerprint: 099a3460e79790844e71afef0f2fc65bb0aa8f3ab3ee0d36facd51677101b570 + category: links + fingerprint: 68af65bffa2855679a0f4313a8fb30a4cb87938a08813988d8da562101462455 count: 1 - kind: file path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/jmx/b0.yml.example sha256: 8d9a12fa3dcbc0661b1512125d8fcb63fe16d27e1839edc723f886e8f8f6ba53 + link_change: replacement cc9c87015e90fe008b18f1d9853e54ba7d7b1eb21c6a9971039128ade73a1891: missing_count: 1 reason: Retained label 'docker-compose.yml' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, @@ -2192,12 +2224,13 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md - category: prose - fingerprint: 53a7f8bb6c355c3a1cfbc1133f81d6c439ddd1e3336cadb7b24ab21665ed2a98 + category: links + fingerprint: 934e672e98ea796373e4840f2c259a0a954e4539c88a560ebe86c50feafe2487 count: 1 - kind: file path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/docker-compose.yml sha256: e6486b8cd25c41b7a5b38004e2d9a680c9599919158857499d61a215310dfeb3 + link_change: replacement 50cb58547b9a5b500c46f975848fcb4e22aef66b2c517dc81ba944f5794afc09: missing_count: 1 reason: Retained label 'prometheus.yml' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, @@ -2207,12 +2240,13 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md - category: prose - fingerprint: ddc1de35507a6b889a07c31a711d342621cd2e082d84a1b52d8a9751403e996d + category: links + fingerprint: d6789f4efbf0446e96ab1b927e394061c31c502ebf9470c565fe78686b072bd4 count: 1 - kind: file path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/prometheus.yml sha256: b32f4f5111347a0f0f897ecb795cdcff69ccb563ceee3080f90a71aeec52bebd + link_change: replacement d8b0873d32bd4f22164ceaffdf91a9df0788a4195931f31b3c8c4f8aa9180b86: missing_count: 1 reason: Retained label 'grafana/provisioning/datasources/prometheus.yml' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, @@ -2222,12 +2256,13 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md - category: prose - fingerprint: 9cc2785f78c81f84cb145c7e6bec527f36fdf0ed93babec1d6cdeda5fc61c79c + category: links + fingerprint: 833581539edf8fe1f89df640c0843204d485c12b4ae66204b76e8d1db871bb98 count: 1 - kind: file path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/provisioning/datasources/prometheus.yml sha256: 19d98cca02290926efd3e8b622d97213f3c19ed9b561898225d8e149ff48d9d6 + link_change: replacement 2f9d7fc6e302cf2d64c80a3af3ad49d6363a4073da7278794d0212cdc69c2171: missing_count: 1 reason: Retained label 'grafana/provisioning/dashboards/provider.yml' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, @@ -2237,12 +2272,13 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md - category: prose - fingerprint: 01913cdfe45180eae604605a28602a0500a8bd870582c179be8a7b1334a20996 + category: links + fingerprint: 8ce82e2bd7413d3daf2b2a388097d3d745402730a4e9168abddefa8380f6dd91 count: 1 - kind: file path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/provisioning/dashboards/provider.yml sha256: 85c52f125f444030ffacf8f2be3854dc6c69e50f9e35327720aa2332e3071d0e + link_change: replacement 4e30c0a736648cdbf3724c22290d2ce4d6e6467ffb8ec18015360f91aa22c5b2: missing_count: 1 reason: Retained label 'kafka-exporter-adv.json' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, @@ -2252,12 +2288,13 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md - category: tables - fingerprint: 2c2b7f6f91a5377e7a17c17d6310fb291834cf0a7337617ae8ec23a5b21d88e7 + category: links + fingerprint: ca9d5e70332e1b46bb72e2bc531049e47b82882d9cc649af0b9da0428cf77515 count: 1 - kind: file path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/dashboards/kafka-exporter-adv.json sha256: 15bda4477eb873e7e333b75265e75c9985c54bbb03324efe8c4ecc08f4f01d92 + link_change: replacement e27b1fb145ebd9254aa8762cf881afc092d7a1388639715e48de4b6b0a492615: missing_count: 1 reason: Retained label 'kafka-jmx-broker.json' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, @@ -2267,12 +2304,13 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md - category: tables - fingerprint: 6173b636503367799735c56b567948813f96c3a8a71793a0ceaeb215de9e6722 + category: links + fingerprint: 29bedea51e83c8e0b273258ca32fe0ee63ed1b066055f20fe4a8dd9795556a21 count: 1 - kind: file path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/prometheus-grafana/grafana/dashboards/kafka-jmx-broker.json sha256: 2863a6a527be917245bf1af98e0bdf3bd4620ded6a58ef7a3dba24279406ef0d + link_change: replacement 9e77ace5a4ee3cf33c393827cfda9dc6a2dfa57024e91cfda8a73e5919c8c10c: missing_count: 1 reason: Replaced retired path label with 'samples/monitoring-assets/images/' in docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md, @@ -2282,8 +2320,8 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/hdinsight-kafka-monitoring/prometheus-grafana/index.md - category: prose - fingerprint: 4170b203da21be0358e01189a49fd4f678801580607a335931505606d0072672 + category: links + fingerprint: 7ca66bd21fd07e0ea1b4cb450c8cc19a6b42df46b255f6a9101d49cb2fa5e5ec count: 1 - kind: file path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images/hdinsight_monitor_integration_1.png @@ -2297,6 +2335,7 @@ documents: - kind: file path: docs/services/azure-monitor/hdinsight-kafka-monitoring/samples/monitoring-assets/images/kafka-jmx-broker.png sha256: aa4b75569d6ed9b26dbb4475e3f3729b56b6e79cbae21e37182040a5e2325240 + link_change: replacement - baseline_path: monitor/otel-monitor-setup.md pages_path: guides/azure-monitor/aks-private-opentelemetry/index.md reviewed_changes: @@ -2997,17 +3036,13 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md - category: prose - fingerprint: 917f2f46ef0ea50d5a7cd20f9c682bc367a935910f63ea1bee92ed35d7b3e340 - count: 1 - - kind: structure - path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md - category: prose - fingerprint: c6e14773fa5ae771f0739befb2096f219e07dead76c904633142ded75f1499b7 - count: 1 + category: links + fingerprint: 140d77653154f7db2d17edcf9fa3ba03c675670cec80e3fd0ccc943d53877f2c + count: 2 - kind: file path: .devcontainer/README.md sha256: 438713fac44d3fe76c97a5878a27575ce9ac2c64576ddba56ba1ea955fcdc998 + link_change: replacement 44028050825bcfa642b14ff4262d4da0b9393687cf19137c36484aa09ff58e8b: missing_count: 3 reason: Replaced retired path label with '에이전트 설정' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, @@ -3017,14 +3052,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md - category: local_link_labels - fingerprint: 048413285eaddfca9b772fb935b9c55881d0e73e90a3cb90fb6e9afac425e872 + category: links + fingerprint: e221506ec2c901bfc94d0354c36eac6f1da24f6d2e7384f63fc1440b5fed4620 count: 3 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/setup/index.md category: title fingerprint: 0c2e310e43b2b80c67123ce079d1d5a0112f23678958ccf29004b869ee1286b6 count: 1 + link_change: replacement 9e7735ab56a3c5447d3e3fced04c1b2fe3a2992cf3180f9ca2b688e7fee0ea64: missing_count: 1 reason: Replaced retired path label with '실측 검증 결과' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, @@ -3034,14 +3070,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md - category: local_link_labels - fingerprint: 09e1dd0d8da0364fe4b0c5df6ff3cce1cbf1a395d8929d1ca3dcd1558eda8a3c + category: links + fingerprint: 217e177f4b3f19ba43f9518a349b8038880a617115148956c72718bb43162a24 count: 1 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md category: title fingerprint: f76180846a8d104c7b8feac0a596c6d997866e148f63262b1c134162043ca7fc count: 1 + link_change: replacement 721d882d49a3cd517405ccc227fe908e4a862088f67bfde974795095f05c02b1: missing_count: 1 reason: Replaced retired path label with 'S1 — HTTP 500 장애' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, @@ -3051,14 +3088,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md - category: local_link_labels - fingerprint: 4de367fbe1f2fb3225642680178bb0f4472b28dc311e1bac2ce77a0c9ac4304b + category: links + fingerprint: 69046480bd9e0ae6bcf3ea00e4df2413231512dd46f1779b69fa57dc6191b016 count: 1 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md category: title fingerprint: e32a47cbcbe792e95607534efe7d450a475dde792e18a3f8ac4abc5d0f935f99 count: 1 + link_change: replacement 5c8bc528ec7649959420de6f956d39d8fe06deebea6a99112373148f0d0e814c: missing_count: 1 reason: Replaced retired path label with 'S2 — 응답 지연' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, @@ -3068,14 +3106,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md - category: local_link_labels - fingerprint: c65505c3efde28e4ea22f2795c06a9bade96c6ef60e0f90797f1f17424ee8c85 + category: links + fingerprint: a190c09ab3da96a63e07209b355514b025a8aaabcb968d836167b7bf0feac0f9 count: 1 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md category: title fingerprint: b4d59aad0c2d13c3c8f8744bd04fc4bf127cb223194fe0fbabed88257b1ef5e9 count: 1 + link_change: replacement 1898c96d3ba8c474502f1860ec20522ea2883d180b44f744e7b6812908fba571: missing_count: 1 reason: Replaced retired path label with 'S3 — Blob 권한 장애' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, @@ -3085,14 +3124,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md - category: local_link_labels - fingerprint: 866bed7aed40500cd651714989e48969e9937ee34775b68892b094daa8674155 + category: links + fingerprint: 4240ad1dacf8c2ea8c7e2a0ec074daba94e7b859b42e9b9fc260066c07aa5c5f count: 1 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md category: title fingerprint: c90dcd7d518f37ad825833cff61808318928723b15dac70d17c8c4f88fef2234 count: 1 + link_change: replacement d0352a09849221675c8996d4f85514fa1067158f15ca0a74d3747d499c347b55: missing_count: 2 reason: Replaced retired path label with '결과 채점' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, @@ -3102,14 +3142,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md - category: local_link_labels - fingerprint: b7c51db89a1538be2c4e2c6e27c40a343406dac73b5e63b4f049462e50ba0b42 + category: links + fingerprint: b7bfe677713910dd4521ea1a54ce1bce563e8a29c1f6f897ff06aba714d5b7ce count: 2 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/results/index.md category: title fingerprint: 9adb1e924965dfde3be5ea5c982704e5b1caa168fa171ee2bb20b1eb003e167f count: 1 + link_change: replacement b342b4cce7c7903c2a29215e5442ef0eb9d9e5d166fbb3a3a5a1a8a280f6d399: missing_count: 1 reason: Replaced retired path label with 'Dynamic Thresholds 확장 설계' in docs/services/azure-monitor/azure-sre-agent/event-lab/index.md, @@ -3119,14 +3160,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md - category: local_link_labels - fingerprint: 27fad307eb79228bedc9cff99543a519c2774bb003e773166c844b63565a231e + category: links + fingerprint: d2d0eacfa039b39525fb2ee32201e738d52a8cd75398ded6dea212e71809211a count: 1 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md category: title fingerprint: 641fa5fe7671aee26dac81baabe5a609793a98d5c33d58fc2f7128dff7816012 count: 1 + link_change: replacement - baseline_path: monitor/sre-agent-event-lab/dynamic-thresholds.md pages_path: guides/azure-monitor/sre-agent-dynamic-thresholds/index.md reviewed_changes: @@ -3155,14 +3197,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/dynamic-thresholds/index.md - category: local_link_labels - fingerprint: 09e1dd0d8da0364fe4b0c5df6ff3cce1cbf1a395d8929d1ca3dcd1558eda8a3c + category: links + fingerprint: 217e177f4b3f19ba43f9518a349b8038880a617115148956c72718bb43162a24 count: 1 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md category: title fingerprint: f76180846a8d104c7b8feac0a596c6d997866e148f63262b1c134162043ca7fc count: 1 + link_change: replacement - baseline_path: monitor/sre-agent-event-lab/guides/01-agent-setup.md pages_path: labs/azure-monitor/sre-agent-event-lab-setup/index.md reviewed_changes: @@ -3219,14 +3262,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/setup/index.md - category: local_link_labels - fingerprint: 005c706d567f8f581f62aac175df7f909e6b3eadda3da97332058e0d1e32684e + category: links + fingerprint: 368b1667a951dd9c93cf58951c8f4cd6f3cb6518a2db65653ef19ad15911a989 count: 1 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/incident-runbook/index.md category: title fingerprint: dfbc98a263caec85142c7a6384e01137129864422a2bfc277e6b70971632b60a count: 1 + link_change: replacement 1c3dbf23991386b472cb4d1276a30837f5c72ce8a85a554953faff7c54e156d3: missing_count: 1 reason: Replaced retired path label with 'S1 — HTTP 500 장애' in docs/services/azure-monitor/azure-sre-agent/setup/index.md, @@ -3236,14 +3280,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/setup/index.md - category: local_link_labels - fingerprint: 4de367fbe1f2fb3225642680178bb0f4472b28dc311e1bac2ce77a0c9ac4304b + category: links + fingerprint: 69046480bd9e0ae6bcf3ea00e4df2413231512dd46f1779b69fa57dc6191b016 count: 1 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md category: title fingerprint: e32a47cbcbe792e95607534efe7d450a475dde792e18a3f8ac4abc5d0f935f99 count: 1 + link_change: replacement - baseline_path: monitor/sre-agent-event-lab/guides/02-scenario-s1.md pages_path: labs/azure-monitor/sre-agent-scenario-http-500/index.md reviewed_changes: @@ -3329,14 +3374,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md - category: local_link_labels - fingerprint: 048413285eaddfca9b772fb935b9c55881d0e73e90a3cb90fb6e9afac425e872 + category: links + fingerprint: e221506ec2c901bfc94d0354c36eac6f1da24f6d2e7384f63fc1440b5fed4620 count: 2 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/setup/index.md category: title fingerprint: 0c2e310e43b2b80c67123ce079d1d5a0112f23678958ccf29004b869ee1286b6 count: 1 + link_change: replacement 642002bebe57cfb2d5d3168236cb52f8f261354812320f7c408f833079661570: missing_count: 1 reason: Replaced retired path label with '결과 채점' in docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md, @@ -3346,14 +3392,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md - category: local_link_labels - fingerprint: b7c51db89a1538be2c4e2c6e27c40a343406dac73b5e63b4f049462e50ba0b42 + category: links + fingerprint: b7bfe677713910dd4521ea1a54ce1bce563e8a29c1f6f897ff06aba714d5b7ce count: 1 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/results/index.md category: title fingerprint: 9adb1e924965dfde3be5ea5c982704e5b1caa168fa171ee2bb20b1eb003e167f count: 1 + link_change: replacement 2c64e239550534f5e89f6403e8338cac2756d397dfbbce90d29af73440d01992: missing_count: 1 reason: Replaced retired path label with 'S2 — 응답 지연' in docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md, @@ -3363,14 +3410,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md - category: local_link_labels - fingerprint: c65505c3efde28e4ea22f2795c06a9bade96c6ef60e0f90797f1f17424ee8c85 + category: links + fingerprint: a190c09ab3da96a63e07209b355514b025a8aaabcb968d836167b7bf0feac0f9 count: 1 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md category: title fingerprint: b4d59aad0c2d13c3c8f8744bd04fc4bf127cb223194fe0fbabed88257b1ef5e9 count: 1 + link_change: replacement - baseline_path: monitor/sre-agent-event-lab/guides/03-scenario-s2.md pages_path: labs/azure-monitor/sre-agent-scenario-latency/index.md reviewed_changes: @@ -3425,14 +3473,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md - category: local_link_labels - fingerprint: 4de367fbe1f2fb3225642680178bb0f4472b28dc311e1bac2ce77a0c9ac4304b + category: links + fingerprint: 69046480bd9e0ae6bcf3ea00e4df2413231512dd46f1779b69fa57dc6191b016 count: 1 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/scenario-http-500/index.md category: title fingerprint: e32a47cbcbe792e95607534efe7d450a475dde792e18a3f8ac4abc5d0f935f99 count: 1 + link_change: replacement 7d81587b0c8906bf8afea33f1abca69106523b68c0d0cd96c1371902e466f702: missing_count: 1 reason: Replaced retired path label with 'S3 — Blob 권한 장애' in docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md, @@ -3442,14 +3491,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md - category: local_link_labels - fingerprint: 866bed7aed40500cd651714989e48969e9937ee34775b68892b094daa8674155 + category: links + fingerprint: 4240ad1dacf8c2ea8c7e2a0ec074daba94e7b859b42e9b9fc260066c07aa5c5f count: 1 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md category: title fingerprint: c90dcd7d518f37ad825833cff61808318928723b15dac70d17c8c4f88fef2234 count: 1 + link_change: replacement - baseline_path: monitor/sre-agent-event-lab/guides/04-scenario-s3.md pages_path: labs/azure-monitor/sre-agent-scenario-blob-permission/index.md reviewed_changes: @@ -3504,14 +3554,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md - category: local_link_labels - fingerprint: c65505c3efde28e4ea22f2795c06a9bade96c6ef60e0f90797f1f17424ee8c85 + category: links + fingerprint: a190c09ab3da96a63e07209b355514b025a8aaabcb968d836167b7bf0feac0f9 count: 1 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/scenario-latency/index.md category: title fingerprint: b4d59aad0c2d13c3c8f8744bd04fc4bf127cb223194fe0fbabed88257b1ef5e9 count: 1 + link_change: replacement 642002bebe57cfb2d5d3168236cb52f8f261354812320f7c408f833079661570: missing_count: 1 reason: Replaced retired path label with '결과 채점' in docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md, @@ -3521,14 +3572,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/scenario-blob-permission/index.md - category: local_link_labels - fingerprint: b7c51db89a1538be2c4e2c6e27c40a343406dac73b5e63b4f049462e50ba0b42 + category: links + fingerprint: b7bfe677713910dd4521ea1a54ce1bce563e8a29c1f6f897ff06aba714d5b7ce count: 1 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/results/index.md category: title fingerprint: 9adb1e924965dfde3be5ea5c982704e5b1caa168fa171ee2bb20b1eb003e167f count: 1 + link_change: replacement - baseline_path: monitor/sre-agent-event-lab/guides/05-results.md pages_path: labs/azure-monitor/sre-agent-results/index.md reviewed_changes: @@ -3743,14 +3795,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md - category: local_link_labels - fingerprint: 13b1e5aa9698261157458e87055433ea4098a13719f65b9e1898b1031d9a965a + category: links + fingerprint: 184b7e1a03820be5298039fa35a06ac1a3bdb14fd519d12333967a0c2c58f91e count: 1 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md category: title fingerprint: d611d20ca77ec1bfca692ac1448fd34d1c59506dab94a52346a0b41750ee0364 count: 1 + link_change: replacement 176e60b305e09e5517119889b188d142890ad06562a56bee19e69327bc4f1709: missing_count: 1 reason: Replaced retired path label with '현재 실습 문서' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, @@ -3760,14 +3813,15 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md - category: local_link_labels - fingerprint: 13b1e5aa9698261157458e87055433ea4098a13719f65b9e1898b1031d9a965a + category: links + fingerprint: 184b7e1a03820be5298039fa35a06ac1a3bdb14fd519d12333967a0c2c58f91e count: 1 - kind: structure path: docs/services/azure-monitor/azure-sre-agent/event-lab/index.md category: title fingerprint: d611d20ca77ec1bfca692ac1448fd34d1c59506dab94a52346a0b41750ee0364 count: 1 + link_change: replacement 4e4283a27c18d5d6aeb2633f6000f9c32ebd13743bcadb62eec08c3451132d55: missing_count: 3 reason: Retained label '결론 frame' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, @@ -3779,9 +3833,19 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md - category: prose - fingerprint: d4088c9a8e6e4233e1176ad0d61b80ac25462786ace36957cac48e2b79d7b229 - count: 3 + category: links + fingerprint: 9aadace542bd23072c923ce5adad7311445a57853cc8119752e356300283c934 + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: links + fingerprint: 29840f412ba4def4266f5acb120071df9e4eb6a950eb84c4d4da9c4db7d4571f + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: links + fingerprint: e5a726481d122c1b55f4fa96797bbb265ac246101a52963edd9561052019a6c5 + count: 1 - kind: file path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/07-conclusion.png sha256: 11b0dd034a0bc72d841e347fd424b8a385340a22d8b04dc0601a0ea77a2d59e1 @@ -3791,6 +3855,7 @@ documents: - kind: file path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/07-conclusion.png sha256: 61dfda52ab8a632c6984a7c8b56a16f06a73fd8dc18decdfa14f977d8879a727 + link_change: replacement 22267b2e1c53a41a357f022e22cd0b554d3413718ed88f6db4c317745ed9ac17: missing_count: 3 reason: Retained label '실제 event timeline' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, @@ -3802,9 +3867,19 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md - category: prose - fingerprint: 2728d7b96640f4527ed5c389d1ce926a624bed99d5efb5200777d48a37211053 - count: 3 + category: links + fingerprint: bee3f25fb61cf92a29e36b6a74f0b2c1501add423c5f6b6e41a2ba57ac7cf7a2 + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: links + fingerprint: af99b33303c4a676133075343097f73602fffded5692c00350c7d0344576ff9c + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: links + fingerprint: 7af164444b6ac5d9758bbf2fe7ad576439c0c342b08926983443c8b4f3813d63 + count: 1 - kind: file path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/timeline.md sha256: 23e41c5a1a3354a2a665b938e1ed5d289a4d3174c7d4aabb484419b965015d42 @@ -3814,6 +3889,7 @@ documents: - kind: file path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.md sha256: 59d7ea731503083635bcbd9196d02e33399214ed31807549e1878e467271229e + link_change: replacement 3efcbd689fedb88f8a060668e0f285de17f2e95ad652571306c7deda71ede7b1: missing_count: 3 reason: Retained label 'Mermaid sequence source' in docs/services/azure-monitor/azure-sre-agent/validation-results/index.md, @@ -3825,9 +3901,19 @@ documents: evidence: - kind: structure path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md - category: prose - fingerprint: 57c926a2687c8505fda40c8c88bd468c271eda7422161b445a712f5a60bcc918 - count: 3 + category: links + fingerprint: fb89775bec29cae9dadc53e2eb7e367dd7cf2a8ab76fcb8cebe734e609eba9ab + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: links + fingerprint: 22a586a0b9539178d411ab113d00c400c5978bf32d2795472118abc5944419f6 + count: 1 + - kind: structure + path: docs/services/azure-monitor/azure-sre-agent/validation-results/index.md + category: links + fingerprint: 2b9b47666b5e100645898fdd775cfc6611eb8df41d74e193cd502008bbf42bd0 + count: 1 - kind: file path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/timeline.mmd sha256: 5a5ded432982d0258fcd39b0a2bbd5fd9697e3ca8cde175b61c93474b7776cfd @@ -3837,6 +3923,7 @@ documents: - kind: file path: docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s3/timeline.mmd sha256: 0e615f6083752517db6f6d59389419a853ba37891ea70cabc63d1d8d08c94aa1 + link_change: replacement - baseline_path: mysql/nodejs/db_upgrade_zero_downtime.md pages_path: guides/azure-database-for-mysql/blue-green-upgrade/index.md reviewed_changes: {} @@ -3869,9 +3956,9 @@ documents: local_link_labels: 782c9f35e32cc1974dd512db107e29fca6fdf72fa3b9a230282acd141f20784c: missing_count: 1 - reason: In docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, section "파일 구성" - drops only the redundant link to this same guide; its title, problem statement and content - are retained at the canonical path. + reason: Removed only the redundant self-link to this same guide from 파일 구성 in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md. + The current canonical document title and file-navigation heading are retained and bound as + identity/topic evidence; no replacement hyperlink is claimed. evidence: - kind: structure path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md @@ -3883,6 +3970,7 @@ documents: category: headings fingerprint: 938c8cc107534cbde9d6d225d2ab9b537981d2c5ac282f37b9eae09fe1d1d522 count: 1 + link_change: redundant-self-link d8340c1aaa9e95e871d626647970820cd4d84b2ebd67c0908548b3af1d912f75: missing_count: 1 reason: Retained label 'ptu_architecture.excalidraw' in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, @@ -3892,12 +3980,13 @@ documents: evidence: - kind: structure path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md - category: prose - fingerprint: 3e830073120fc23618766188ea6b70e020110f3dbe0dac31666df8b1179fe928 + category: links + fingerprint: 86c2acee570d15a23788475adbe416ff82772ef2d01afaf2df5770361d67a1ef count: 1 - kind: file path: docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/ptu_architecture.excalidraw sha256: bc33797780dfd0e8c7e48851dab8664bfd6e859450cd38b560363197a67daea4 + link_change: replacement 3780ddb85f0f2468a666925639ce03196d37651ae07198d5f07178660b4cb73a: missing_count: 1 reason: Retained label 'ptu_architecture.png' in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, @@ -3907,12 +3996,13 @@ documents: evidence: - kind: structure path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md - category: prose - fingerprint: 14d0d227e1084b1a72c7632456af41c566c110429826a37a786558cfd3f1263c + category: links + fingerprint: 031821ab9eeaf49aa716655750cf7b94cd6d407ffa2a825e6f267701ed150948 count: 1 - kind: file path: docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/ptu_architecture.png sha256: accf9178699bee9407e694335ce1e975a7a8954338ec9ff4db73bbda27822ac4 + link_change: replacement a44631d85720594c37c72f21f5d6086dd8e36ef4aa4df5d6ef8084402a49354b: missing_count: 1 reason: Retained label 'render_excalidraw.py' in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, @@ -3922,12 +4012,13 @@ documents: evidence: - kind: structure path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md - category: prose - fingerprint: f0908da93312da0f7632ed8e2a4dc82724d67d126b0e98bcd61e1cee6e7965de + category: links + fingerprint: ba4fc57980924c885e34c006daaf4b14938387edc5d6edf6ea04c2d4db917113 count: 1 - kind: file path: docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/render_excalidraw.py sha256: ebfaf59455909722b8d447042ac84a03428c4c301f3200bfa8c0b4a8024391ae + link_change: replacement 7ee017639b4fc20bb4aa51df646d0915fee435c7d2b1fb7422ac484d9d035c99: missing_count: 1 reason: Retained label 'runbooks/adaptive_tpm_quota_controller.py' in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, @@ -3937,12 +4028,13 @@ documents: evidence: - kind: structure path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md - category: prose - fingerprint: 1ef31cc4e68d401971b1ce6bc4b548d3e5f4b75f7066e62c8843c13ae427e89a + category: links + fingerprint: 1581464a84ac6e5e5728cbb9d252c39ce99f17c5fd05c8bf73e521afe3a8f182 count: 1 - kind: file path: docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/adaptive_tpm_quota_controller.py sha256: 391e561774bb963e90b88a78693c2aaf17708c84ea909244ad34a0a38e162f57 + link_change: replacement 59e441dc4125b74571fdfbbe9d389c5f81a03e3b76542f71a4b770af7cbab02d: missing_count: 1 reason: Retained label 'runbooks/requirements.txt' in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, @@ -3952,12 +4044,13 @@ documents: evidence: - kind: structure path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md - category: prose - fingerprint: 530a02263603ab44681201b88f5ee4a9e4fd721fe1431f503e256d9c9c483254 + category: links + fingerprint: 4610a13cd2acf7c3ab740360af1ac787c8f9b72e6857042f4e3b9c1949512a79 count: 1 - kind: file path: docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/requirements.txt sha256: 49656d26230e95250bb6c16864b4df682b929c08fa0858cd94873d87e69c7e40 + link_change: replacement 83d324c4a28e9157567e4fd24234bcd1f42e9974ac6778197bb506e0a84deda3: missing_count: 1 reason: Retained label 'runbooks/optional_adaptive_tpm_quota_controller.ps1' in docs/services/azure-openai/adaptive-ptu-load-balancing/index.md, @@ -3967,12 +4060,13 @@ documents: evidence: - kind: structure path: docs/services/azure-openai/adaptive-ptu-load-balancing/index.md - category: prose - fingerprint: 39cf3ee98fc6179a6a40922c588e59e12ca835e0a4aa2ce817c2848780ebe57c + category: links + fingerprint: d9255295f2005fb89e29e3f624911de988430a1f50a24cb86a41657569f2f1e7 count: 1 - kind: file path: docs/services/azure-openai/adaptive-ptu-load-balancing/samples/runbooks/runbooks/optional_adaptive_tpm_quota_controller.ps1 sha256: 3f8bb6d4736fd7b8672bc6c68d26cabbc18c67826460c711d17c94afbe162f71 + link_change: replacement - baseline_path: redis/nodejs/cluster_failover_recovery.md pages_path: cases/azure-managed-redis/cluster-failover-recovery/index.md reviewed_changes: @@ -3986,8 +4080,8 @@ documents: evidence: - kind: structure path: docs/services/azure-managed-redis/cluster-failover-recovery/index.md - category: prose - fingerprint: 7497a1325f7f9f9927207957ebcd4b861fe31b08cd25e66b9df7ccbe1d3c155d + category: links + fingerprint: 90d4edf130c94626505f5a433eff1c88becab816e468bc916c922b603c0f31a8 count: 1 - kind: file path: docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/README.md @@ -4010,6 +4104,7 @@ documents: - kind: file path: docs/services/azure-managed-redis/cluster-failover-recovery/samples/nodejs-cluster/cluster_example/server.js sha256: bcba13f99b4ec31285b491659e26baee3fec98256e9cf068d248446ebfbdae96 + link_change: replacement dispositions: .azure/deployment-plan.md: status: excluded-local-state diff --git a/tests/docs/test_pre_pages.py b/tests/docs/test_pre_pages.py index ac5bfc1d..a661ec1a 100644 --- a/tests/docs/test_pre_pages.py +++ b/tests/docs/test_pre_pages.py @@ -63,7 +63,7 @@ def history(tmp_path: Path) -> tuple[Path, str, str]: @pytest.fixture def manifest_data() -> dict: return { - "version": 2, + "version": 3, "baseline_commit": BASELINE, "pages_commit": PAGES, "rename_similarity": 20, @@ -165,7 +165,7 @@ def test_inventory_requires_all_top_level_fields( @pytest.mark.parametrize( ("field", "value"), [ - ("extra", True), ("version", 1), ("version", True), + ("extra", True), ("version", 1), ("version", 2), ("version", True), ("rename_similarity", 0), ("rename_similarity", 101), ("rename_similarity", "20"), ("rename_similarity", True), ("documents", {}), ("dispositions", []), @@ -258,7 +258,7 @@ def test_inventory_reports_invalid_or_unsafe_yaml(tmp_path: Path, text: str) -> @pytest.fixture def raw_inventory() -> str: - return f"""version: 2 + return f"""version: 3 baseline_commit: {BASELINE} pages_commit: {PAGES} rename_similarity: 20 @@ -288,7 +288,7 @@ def raw_inventory() -> str: @pytest.mark.parametrize( ("original", "replacement", "key"), [ - ("version: 2", "version: 1\nversion: 2", "version"), + ("version: 3", "version: 2\nversion: 3", "version"), ( " - baseline_path: old/guide.md", " - baseline_path: ignored.md\n baseline_path: old/guide.md", diff --git a/tests/docs/test_pre_pages_content.py b/tests/docs/test_pre_pages_content.py index 1502d17d..e6d1d5a4 100644 --- a/tests/docs/test_pre_pages_content.py +++ b/tests/docs/test_pre_pages_content.py @@ -7,6 +7,7 @@ from pathlib import Path, PurePosixPath import re import shutil +import subprocess import pytest import yaml @@ -28,10 +29,27 @@ ROOT = Path(__file__).parents[2] BASELINE_PATH = PurePosixPath("old/guide.md") CURRENT_PATH = PurePosixPath("docs/services/service/topic/index.md") -REASON = ( - "Preserved in docs/services/service/topic/samples/evidence/README.md " - "under Historical commands; the canonical guide now links to that evidence." +REASON_TEMPLATE = ( + "The historical command parameters and execution context are preserved " + "in the documented replacement procedure at {path}." ) +REASON = REASON_TEMPLATE.format(path=CURRENT_PATH) +_DEFAULT_REASON = object() + + +def git(repo: Path, *arguments: str) -> bytes: + return subprocess.run( + ["git", "-C", str(repo), *arguments], check=True, capture_output=True, + ).stdout + + +def track_files(repo: Path, *paths: str) -> None: + if not (repo / ".git").exists(): + git(repo, "init", "-q") + git(repo, "config", "user.name", "Evidence Tests") + git(repo, "config", "user.email", "evidence@example.com") + git(repo, "add", "--", *paths) + git(repo, "-c", "commit.gpgsign=false", "commit", "-qm", "Track public evidence") def fingerprint(category: str, value: str) -> str: @@ -45,17 +63,25 @@ def structure_evidence( return {"kind": "structure", "path": path, "category": category, "fingerprint": fingerprint(category, value), "count": count} -def approval(reason=REASON, missing_count=1, evidence=None) -> dict: - return { +def approval(reason=_DEFAULT_REASON, missing_count=1, evidence=None, link_change=None) -> dict: + references = [structure_evidence()] if evidence is None else evidence + if reason is _DEFAULT_REASON: + path = references[0].get("path", CURRENT_PATH) if references and isinstance(references[0], dict) else CURRENT_PATH + reason = REASON_TEMPLATE.format(path=path) + result = { "missing_count": missing_count, "reason": reason, - "evidence": [structure_evidence()] if evidence is None else evidence, + "evidence": references, } + if link_change is not None: + result["link_change"] = link_change + return result def proof_file(repo: Path, text: str = "Replacement.", path: str = str(CURRENT_PATH)) -> Path: file = repo / path file.parent.mkdir(parents=True, exist_ok=True) file.write_text(text, encoding="utf-8") + track_files(repo, path) return file @@ -88,6 +114,92 @@ def test_inline_links_keep_labels_not_destinations_or_titles() -> None: assert second.local_link_labels == ("공식 문서",) +def test_link_evidence_keeps_normalized_label_and_complete_explicit_destination() -> None: + structure = extract_markdown_structure( + '[ 가이드 A ](<../Target/index.md?q=1&lang=ko#Section> "display title")' + ) + assert structure.links == ("가이드 A\n../Target/index.md?q=1&lang=ko#Section",) + + +def test_link_evidence_retains_destination_differences_and_multiplicity() -> None: + structure = extract_markdown_structure( + "[Same](https://example.com/A?q=1#part) " + "[Same](https://example.com/A?q=1#part) " + "[Same](https://example.com/A?q=2#other)" + ) + assert Counter(structure.links) == { + "Same\nhttps://example.com/A?q=1#part": 2, + "Same\nhttps://example.com/A?q=2#other": 1, + } + assert fingerprint("links", structure.links[0]) != fingerprint("links", structure.links[2]) + + +def test_links_are_current_evidence_only_not_baseline_destination_comparison() -> None: + before = extract_markdown_structure("[Guide](../old.md)") + after = extract_markdown_structure("[Guide](../new.md)") + assert before.links != after.links + assert before.prose == after.prose + assert compare_markdown(BASELINE_PATH, CURRENT_PATH, before, after, {}).status == "preserved" + + +def test_link_evidence_resolves_full_collapsed_and_shortcut_references() -> None: + structure = extract_markdown_structure( + "[Guide][g] [More][] [Short]\n\n" + "[g]: ../guide.md#setup\n[More]: https://example.com/help\n[Short]: #details" + ) + assert structure.links == ( + "Guide\n../guide.md#setup", "More\nhttps://example.com/help", "Short\n#details", + ) + + +def test_link_evidence_excludes_images_comments_and_code_literals() -> None: + structure = extract_markdown_structure( + "![Image](image.png) [![Logo](logo.svg)](https://example.com)\n\n" + "![Reference image][asset]\n\n[asset]: image.svg\n\n" + 'HTML image\n\n' + "`[Literal](literal.md)`\n\n```md\n[Code](code.md)\n```\n\n" + "\n\n[Visible](visible.md)" + ) + assert structure.links == ("Visible\nvisible.md",) + + +def test_link_evidence_keeps_inline_code_label_text_not_placeholder_tokens() -> None: + structure = extract_markdown_structure("[`A_B`](../target.md)") + assert structure.links == ("A_B\n../target.md",) + + +@pytest.mark.parametrize("text", [ + 'Guide', + "Guide", + '\nGuide\n', +]) +def test_link_evidence_recognizes_html_anchors(text: str) -> None: + assert extract_markdown_structure(text).links == ("Guide\n../target.md?a=1&b=2#part",) + + +def test_link_evidence_recognizes_explicit_autolinks_but_not_placeholder_tags() -> None: + structure = extract_markdown_structure(" ") + assert structure.links == ("https://example.com/a#b\nhttps://example.com/a#b",) + + +def test_plain_label_text_is_not_link_evidence() -> None: + assert extract_markdown_structure("tei-adapter").links == () + + +@pytest.mark.parametrize("text", [ + r"\[Adapter](https://example.com/adapter)", + "\\[Adapter][ref]\n\n[ref]: https://example.com/adapter", + r"\", + r'\Adapter', +]) +def test_escaped_link_markup_does_not_count_as_a_current_hyperlink(text: str) -> None: + assert extract_markdown_structure(text).links == () + + +def test_even_backslashes_before_a_link_do_not_escape_its_markup() -> None: + assert extract_markdown_structure(r"\\[Adapter](target.md)").links == ("Adapter\ntarget.md",) + + def test_reference_links_and_images_use_definitions_not_definition_prose() -> None: structure = extract_markdown_structure( "# Title\n\n[Guide][g] and [More][] and [Short].\n\n![Diagram][img]\n\n" @@ -331,7 +443,7 @@ def test_a_path_alone_does_not_make_a_generic_updated_reason_specific() -> None: def test_specific_korean_preservation_reasons_are_valid(tmp_path: Path) -> None: reason = ( - "docs/services/service/topic/samples/evidence/README.md의 이관 전 조사 명령 절에 " + "docs/services/service/topic/index.md의 이관 전 조사 명령 절에 " "원문을 보존하고 현재 가이드에서 그 역사적 증거로 연결한다." ) proof_file(tmp_path) @@ -342,7 +454,7 @@ def test_specific_korean_preservation_reasons_are_valid(tmp_path: Path) -> None: @pytest.mark.parametrize("key", ["hash", "A" * 64, "g" * 64, "a" * 63, 1]) def test_inventory_rejects_non_sha256_exception_keys(tmp_path: Path, key: object) -> None: data = yaml.safe_load((ROOT / "scripts/docs/pre_pages_inventory.yml").read_text()) - data["version"] = 2 + data["version"] = 3 data["documents"][0]["reviewed_changes"] = {"prose": {key: approval()}} path = tmp_path / "inventory.yml" path.write_text(yaml.safe_dump(data, allow_unicode=True), encoding="utf-8") @@ -352,7 +464,7 @@ def test_inventory_rejects_non_sha256_exception_keys(tmp_path: Path, key: object def test_inventory_rejects_unknown_exception_categories(tmp_path: Path) -> None: data = yaml.safe_load((ROOT / "scripts/docs/pre_pages_inventory.yml").read_text()) - data["version"] = 2 + data["version"] = 3 data["documents"][0]["reviewed_changes"] = {"anything": {"a" * 64: approval()}} path = tmp_path / "inventory.yml" path.write_text(yaml.safe_dump(data, allow_unicode=True), encoding="utf-8") @@ -432,6 +544,7 @@ def isolated_real_document_audit( entry = next(doc for doc in inventory.documents if str(doc.baseline_path) == baseline) current_path = tmp_path / current shutil.copytree((ROOT / current).parent, current_path.parent) + track_files(tmp_path, str(current_path.parent.relative_to(tmp_path))) monkeypatch.setattr( pre_pages_content, "resolve_current_documents", lambda repo, manifest: {entry.baseline_path: load_document(current_path, tmp_path / "docs")}, @@ -551,7 +664,7 @@ def test_duplicate_references_are_rejected_even_if_counts_or_hashes_differ(kind: if kind == "structure": first, second = structure_evidence(), structure_evidence(count=2) else: - first = {"kind": "file", "path": "docs/evidence.bin", "sha256": "a" * 64} + first = {"kind": "file", "path": "docs/services/service/topic/evidence.bin", "sha256": "a" * 64} second = {**first, "sha256": "b" * 64} with pytest.raises(AuditFormatError, match="duplicate"): validate_reviewed_changes({"prose": {fingerprint("prose", "Removed."): approval(evidence=[first, second])}}) @@ -598,7 +711,10 @@ def test_generic_only_reasons_cannot_hide_behind_english_or_korean_prefixes(reas "Nginx 프록시의 Host 헤더를 예시 도메인으로 치환하고 기존 upstream 포트와 전달 설정을 보존한다.", ], ) -def test_concrete_reasons_do_not_need_to_repeat_paths_already_bound_as_evidence(reason: str) -> None: +def test_concrete_reasons_must_name_an_exact_evidence_path(reason: str) -> None: + with pytest.raises(AuditFormatError, match="reason.*path"): + validate_reviewed_changes({"prose": {fingerprint("prose", "Removed."): approval(reason)}}) + reason += f" Evidence: {CURRENT_PATH}." parsed = validate_reviewed_changes({"prose": {fingerprint("prose", "Removed."): approval(reason)}}) assert parsed["prose"][fingerprint("prose", "Removed.")].reason == reason @@ -648,10 +764,13 @@ def test_evidence_occurrence_count_requires_the_exact_current_multiplicity(tmp_p @pytest.mark.parametrize("mutation", ["change", "delete"]) def test_binary_file_evidence_requires_the_exact_current_sha256(tmp_path: Path, mutation: str) -> None: - path = tmp_path / "evidence.bin" + relative = "docs/services/service/topic/samples/evidence/payload.bin" + path = tmp_path / relative + path.parent.mkdir(parents=True) original = b"\x00\xffhistorical capture" path.write_bytes(original) - ref = {"kind": "file", "path": "evidence.bin", "sha256": sha256(original).hexdigest()} + track_files(tmp_path, relative) + ref = {"kind": "file", "path": relative, "sha256": sha256(original).hexdigest()} reviewed = {"prose": {fingerprint("prose", "Removed."): approval(evidence=[ref])}} assert compare("Removed.", "", reviewed, repo_root=tmp_path).status == "reviewed" if mutation == "delete": @@ -666,9 +785,13 @@ def test_binary_file_evidence_requires_the_exact_current_sha256(tmp_path: Path, def test_evidence_cannot_escape_the_repository_through_a_symlink(tmp_path: Path, kind: str) -> None: outside = tmp_path.parent / f"{tmp_path.name}-outside.md" outside.write_text("Replacement.") - (tmp_path / "link.md").symlink_to(outside) - ref = structure_evidence(path="link.md") if kind == "structure" else { - "kind": "file", "path": "link.md", "sha256": sha256(outside.read_bytes()).hexdigest(), + relative = "docs/services/service/topic/link.md" + path = tmp_path / relative + path.parent.mkdir(parents=True) + path.symlink_to(outside) + track_files(tmp_path, relative) + ref = structure_evidence(path=relative) if kind == "structure" else { + "kind": "file", "path": relative, "sha256": sha256(outside.read_bytes()).hexdigest(), } with pytest.raises(AuditFormatError, match="outside"): compare("Removed.", "", {"prose": {fingerprint("prose", "Removed."): approval(evidence=[ref])}}, repo_root=tmp_path) @@ -700,3 +823,308 @@ def test_each_historical_sre_thread_approval_binds_its_corresponding_scenario() and ref.fingerprint == fingerprint("prose", f"- Agent thread: ") for ref in change.evidence ) + + +def test_removing_real_tei_adapter_link_markup_invalidates_its_approval( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, +) -> None: + inventory, path = isolated_real_document_audit( + tmp_path, monkeypatch, "aisearch/custom_vectorization/01_custom_embedding_guide.md", + "docs/services/azure-ai-search/custom-vectorization/index.md", + ) + assert not audit_document_content(tmp_path, inventory)[0].missing + link = ( + "[tei-adapter](https://github.com/hellices/devguidesample/tree/main/" + "docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter)" + ) + text = path.read_text() + assert text.count(link) == 1 + path.write_text(text.replace(link, "tei-adapter")) + assert (path.parent / "samples/implementation/tei-adapter/app.py").is_file() + with pytest.raises(AuditFormatError, match="evidence|links"): + audit_document_content(tmp_path, inventory) + + +def test_ignored_baseline_snapshot_cannot_substitute_for_current_public_evidence(tmp_path: Path) -> None: + proof_file(tmp_path) + (tmp_path / ".gitignore").write_text(".superpowers/\n") + snapshot = tmp_path / ".superpowers/sdd/baseline.md" + snapshot.parent.mkdir(parents=True) + snapshot.write_text("Removed.") + reason = ( + "The historical diagnostic commands are retained in .superpowers/sdd/baseline.md " + "as replacement evidence for the removed guide section." + ) + reviewed = {"prose": {fingerprint("prose", "Removed."): approval( + reason, evidence=[structure_evidence("Removed.", path=".superpowers/sdd/baseline.md")], + )}} + with pytest.raises(AuditFormatError, match="public|eligible|forbidden|tracked"): + compare("Removed.", "", reviewed, repo_root=tmp_path) + + +def test_git_metadata_cannot_be_used_as_file_evidence(tmp_path: Path) -> None: + proof_file(tmp_path) + metadata = tmp_path / ".git/HEAD" + ref = {"kind": "file", "path": ".git/HEAD", "sha256": sha256(metadata.read_bytes()).hexdigest()} + reason = ( + "The current .git/HEAD file is preserved as replacement evidence for the removed " + "deployment procedure and its execution context." + ) + with pytest.raises(AuditFormatError, match="public|eligible|forbidden|tracked"): + compare("Removed.", "", {"prose": {fingerprint("prose", "Removed."): approval(reason, evidence=[ref])}}, repo_root=tmp_path) + + +@pytest.mark.parametrize("include_path", [False, True]) +@pytest.mark.parametrize("reason", [ + "Safety: this obsolete material is irrelevant and therefore unnecessary.", + "Nothing significant has changed; approved after review.", + "Replaced with a suitable equivalent for improved presentation.", +]) +def test_rereview_generic_examples_are_rejected_even_with_an_exact_path(reason: str, include_path: bool) -> None: + if include_path: + reason += f" Evidence: {CURRENT_PATH}." + with pytest.raises(AuditFormatError, match="reason"): + validate_reviewed_changes({"prose": {fingerprint("prose", "Removed."): approval(reason)}}) + + +@pytest.mark.parametrize("category", ["prose", "local_link_labels", "title"]) +def test_replacement_link_approvals_require_destination_bound_links_evidence(category: str) -> None: + change = approval(evidence=[structure_evidence("Adapter", category)]) + with pytest.raises(AuditFormatError, match="links"): + validate_reviewed_changes({"local_link_labels": {fingerprint("local_link_labels", "Adapter"): change}}) + + +def test_links_cannot_be_used_as_a_baseline_exception_category() -> None: + with pytest.raises(AuditFormatError, match="category"): + validate_reviewed_changes({"links": {fingerprint("links", "Adapter\nold.md"): approval()}}) + + +@pytest.mark.parametrize("mutation", ["plain-label", "changed-destination", "changed-fragment"]) +def test_exact_current_link_evidence_invalidates_on_markup_or_destination_changes( + tmp_path: Path, mutation: str, +) -> None: + current = "[Adapter](https://example.com/adapter#setup)" + path = proof_file(tmp_path, current) + change = approval(evidence=[structure_evidence("Adapter\nhttps://example.com/adapter#setup", "links")]) + reviewed = {"local_link_labels": {fingerprint("local_link_labels", "Adapter"): change}} + assert compare("[Adapter](adapter.md)", current, reviewed, repo_root=tmp_path).status == "reviewed" + changed = { + "plain-label": "Adapter", + "changed-destination": "[Adapter](https://example.com/other#setup)", + "changed-fragment": "[Adapter](https://example.com/adapter#other)", + }[mutation] + path.write_text(changed) + with pytest.raises(AuditFormatError, match="evidence.*links|links.*count"): + compare("[Adapter](adapter.md)", changed, reviewed, repo_root=tmp_path) + + +def test_link_evidence_requires_exact_current_multiplicity(tmp_path: Path) -> None: + current = "[Adapter](https://example.com/adapter)" + path = proof_file(tmp_path, current + "\n\n" + current) + change = approval(missing_count=2, evidence=[ + structure_evidence("Adapter\nhttps://example.com/adapter", "links", count=2), + ]) + reviewed = {"local_link_labels": {fingerprint("local_link_labels", "Adapter"): change}} + baseline = "[Adapter](adapter.md)\n\n[Adapter](adapter.md)" + assert compare(baseline, path.read_text(), reviewed, repo_root=tmp_path).status == "reviewed" + path.write_text(current + "\n\nAdapter") + with pytest.raises(AuditFormatError, match="count"): + compare(baseline, path.read_text(), reviewed, repo_root=tmp_path) + + +def test_duplicate_link_evidence_is_rejected_even_with_different_counts() -> None: + first = structure_evidence("Adapter\nhttps://example.com/adapter", "links") + change = approval(evidence=[first, {**first, "count": 2}]) + with pytest.raises(AuditFormatError, match="duplicate"): + validate_reviewed_changes({"local_link_labels": {fingerprint("local_link_labels", "Adapter"): change}}) + + +@pytest.mark.parametrize("count", [0, -1, True, "1"]) +def test_link_evidence_counts_must_be_positive_integers(count: object) -> None: + change = approval(evidence=[structure_evidence("Adapter\nhttps://example.com/adapter", "links", count=count)]) + with pytest.raises(AuditFormatError, match="count"): + validate_reviewed_changes({"local_link_labels": {fingerprint("local_link_labels", "Adapter"): change}}) + + +def self_link_approval() -> dict: + return approval( + f"The redundant self-link to this same guide is removed; its current title and Files navigation are retained in {CURRENT_PATH}.", + evidence=[ + structure_evidence("Current guide", "title"), + structure_evidence("Files", "headings"), + ], + link_change="redundant-self-link", + ) + + +def test_intentionally_removed_self_link_is_bound_to_current_document_identity(tmp_path: Path) -> None: + current = "# Current guide\n\n## Files\n\nguide.md" + proof_file(tmp_path, current) + baseline = current.replace("\nguide.md", "\n[guide.md](guide.md)") + reviewed = {"local_link_labels": {fingerprint("local_link_labels", "guide.md"): self_link_approval()}} + assert compare(baseline, current, reviewed, repo_root=tmp_path).status == "reviewed" + + +@pytest.mark.parametrize("destination", ["other.md", "guide.md#files", "guide.md?view=text"]) +def test_self_link_removal_exception_cannot_approve_another_target_or_section( + tmp_path: Path, destination: str, +) -> None: + current = "# Current guide\n\n## Files\n\nguide.md" + proof_file(tmp_path, current) + baseline = current.replace("\nguide.md", f"\n[guide.md]({destination})") + with pytest.raises(AuditFormatError, match="self-link"): + compare(baseline, current, {"local_link_labels": { + fingerprint("local_link_labels", "guide.md"): self_link_approval(), + }}, repo_root=tmp_path) + + +def test_self_link_removal_cannot_bind_only_an_unrelated_document(tmp_path: Path) -> None: + current = "# Current guide\n\n## Files\n\nguide.md" + proof_file(tmp_path, current) + other = "docs/services/service/other/index.md" + proof_file(tmp_path, current, other) + change = self_link_approval() + for reference in change["evidence"]: + reference["path"] = other + change["reason"] += f" Identity evidence: {other}." + with pytest.raises(AuditFormatError, match="self-link.*current|current.*identity"): + compare(current.replace("\nguide.md", "\n[guide.md](guide.md)"), current, { + "local_link_labels": {fingerprint("local_link_labels", "guide.md"): change}, + }, repo_root=tmp_path) + + +def test_self_link_removal_requires_both_title_and_topic_structure() -> None: + change = self_link_approval() + change["evidence"] = change["evidence"][:1] + with pytest.raises(AuditFormatError, match="self-link|identity"): + validate_reviewed_changes({"local_link_labels": {fingerprint("local_link_labels", "guide.md"): change}}) + + +def test_self_link_removal_cannot_pretend_an_unrelated_replacement_hyperlink_exists() -> None: + change = self_link_approval() + change["evidence"].append(structure_evidence("Other\nother.md", "links")) + with pytest.raises(AuditFormatError, match="self-link"): + validate_reviewed_changes({"local_link_labels": {fingerprint("local_link_labels", "guide.md"): change}}) + + +def test_self_link_removal_requires_a_specific_removal_reason() -> None: + change = self_link_approval() + change["reason"] = f"The Nginx Host header retains the example domain and upstream port in {CURRENT_PATH}." + with pytest.raises(AuditFormatError, match="self-link.*reason|reason.*self-link"): + validate_reviewed_changes({"local_link_labels": {fingerprint("local_link_labels", "guide.md"): change}}) + + +@pytest.mark.parametrize("category", ["prose", "code", "tables"]) +def test_link_change_exemptions_are_invalid_on_non_link_categories(category: str) -> None: + with pytest.raises(AuditFormatError, match="link_change"): + validate_reviewed_changes({category: {fingerprint(category, "Removed."): self_link_approval()}}) + + +@pytest.mark.parametrize("tracked", [False, True]) +def test_ignored_public_file_is_ineligible_even_if_force_tracked_at_head(tmp_path: Path, tracked: bool) -> None: + proof_file(tmp_path) + relative = "docs/services/service/topic/ignored.md" + path = tmp_path / relative + path.write_text("Replacement.") + (tmp_path / ".gitignore").write_text("**/ignored.md\n") + if tracked: + git(tmp_path, "add", "-f", "--", relative) + git(tmp_path, "-c", "commit.gpgsign=false", "commit", "-qm", "Track ignored fixture") + reviewed = {"prose": {fingerprint("prose", "Removed."): approval(evidence=[structure_evidence(path=relative)])}} + with pytest.raises(AuditFormatError, match="ignored|HEAD"): + compare("Removed.", "", reviewed, repo_root=tmp_path) + + +@pytest.mark.parametrize("staged", [False, True]) +def test_untracked_or_index_only_public_file_is_not_head_evidence(tmp_path: Path, staged: bool) -> None: + proof_file(tmp_path) + relative = "docs/services/service/topic/untracked.md" + (tmp_path / relative).write_text("Replacement.") + if staged: + git(tmp_path, "add", "--", relative) + reviewed = {"prose": {fingerprint("prose", "Removed."): approval(evidence=[structure_evidence(path=relative)])}} + with pytest.raises(AuditFormatError, match="HEAD"): + compare("Removed.", "", reviewed, repo_root=tmp_path) + + +@pytest.mark.parametrize("relative", [ + "README.md", "scripts/snapshot.md", "baseline/guide.md", + ".devcontainer/README.md.backup", "docs/services/service/topic/.superpowers/snapshot.md", +]) +def test_head_tracked_paths_outside_the_explicit_public_allowlist_are_ineligible( + tmp_path: Path, relative: str, +) -> None: + proof_file(tmp_path, "Replacement.", relative) + with pytest.raises(AuditFormatError, match="eligible|public|forbidden"): + compare("Removed.", "", {"prose": {fingerprint("prose", "Removed."): approval( + evidence=[structure_evidence(path=relative)], + )}}, repo_root=tmp_path) + + +def test_exact_tracked_devcontainer_readme_is_valid_and_working_tree_mutation_is_detected(tmp_path: Path) -> None: + relative = ".devcontainer/README.md" + file = proof_file(tmp_path, "Developer setup commands.", relative) + ref = {"kind": "file", "path": relative, "sha256": sha256(file.read_bytes()).hexdigest()} + reviewed = {"prose": {fingerprint("prose", "Removed."): approval(evidence=[ref])}} + assert compare("Removed.", "", reviewed, repo_root=tmp_path).status == "reviewed" + file.write_text("Developer commands deleted.") + with pytest.raises(AuditFormatError, match="evidence.*SHA-256"): + compare("Removed.", "", reviewed, repo_root=tmp_path) + + +def test_public_tracked_symlink_cannot_resolve_to_a_private_snapshot(tmp_path: Path) -> None: + proof_file(tmp_path) + snapshot = tmp_path / ".superpowers/sdd/baseline.md" + snapshot.parent.mkdir(parents=True) + snapshot.write_text("Replacement.") + relative = "docs/services/service/topic/alias.md" + (tmp_path / relative).symlink_to(snapshot) + track_files(tmp_path, relative) + with pytest.raises(AuditFormatError, match="public|eligible|forbidden|HEAD"): + compare("Removed.", "", {"prose": {fingerprint("prose", "Removed."): approval( + evidence=[structure_evidence(path=relative)], + )}}, repo_root=tmp_path) + + +def test_public_symlink_to_public_head_tracked_source_preserves_mutation_checks(tmp_path: Path) -> None: + target = proof_file(tmp_path) + relative = "docs/services/service/topic/alias.md" + (tmp_path / relative).symlink_to(target) + track_files(tmp_path, relative) + reviewed = {"prose": {fingerprint("prose", "Removed."): approval(evidence=[structure_evidence(path=relative)])}} + assert compare("Removed.", "", reviewed, repo_root=tmp_path).status == "reviewed" + target.write_text("Changed.") + with pytest.raises(AuditFormatError, match="evidence"): + compare("Removed.", "", reviewed, repo_root=tmp_path) + + +def test_worktree_git_pointer_is_not_public_file_evidence(tmp_path: Path) -> None: + proof_file(tmp_path) + linked = tmp_path / "linked" + git(tmp_path, "worktree", "add", "-q", "--detach", str(linked)) + metadata = linked / ".git" + assert metadata.is_file() + ref = {"kind": "file", "path": ".git", "sha256": sha256(metadata.read_bytes()).hexdigest()} + with pytest.raises(AuditFormatError, match="public|eligible|forbidden"): + compare("Removed.", "", {"prose": {fingerprint("prose", "Removed."): approval( + "The exact .git worktree pointer is retained as evidence for the removed deployment procedure.", + evidence=[ref], + )}}, repo_root=linked) + + +@pytest.mark.parametrize("mentioned", [ + "docs/services/service/topic/index.md.backup", + "docs/services/service/topic/index.md/another", + "docs/services/service/topic/INDEX.md", +]) +def test_reason_must_name_an_exact_reference_not_a_prefix_or_different_case(mentioned: str) -> None: + reason = f"The Nginx Host header retains its example domain and upstream port in {mentioned}." + with pytest.raises(AuditFormatError, match="reason.*path"): + validate_reviewed_changes({"prose": {fingerprint("prose", "Removed."): approval(reason)}}) + + +def test_devcontainer_path_itself_cannot_make_a_generic_reason_concrete() -> None: + reference = structure_evidence(path=".devcontainer/README.md") + reason = "Safety: updated and no longer needed for this content. Evidence: .devcontainer/README.md." + with pytest.raises(AuditFormatError, match="reason"): + validate_reviewed_changes({"prose": {fingerprint("prose", "Removed."): approval(reason, evidence=[reference])}}) From d28b8c5aa0743d83f2dd0205635cfdf844cd79e0 Mon Sep 17 00:00:00 2001 From: hellices Date: Mon, 14 Sep 2026 02:55:43 +0900 Subject: [PATCH 08/26] fix(docs): isolate HTML and honor image escape parity Tokenize HTML tags, attributes, and raw containers before Markdown scans so hidden syntax cannot forge current evidence. Read genuine element attributes and rendered anchor text, and honor image escape parity across inline, reference, and nested links. Regress both real bypasses while preserving all 235 approvals unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/docs/pre_pages_content.py | 298 +++++++++++++++++++++------ tests/docs/test_pre_pages_content.py | 216 ++++++++++++++++++- 2 files changed, 454 insertions(+), 60 deletions(-) diff --git a/scripts/docs/pre_pages_content.py b/scripts/docs/pre_pages_content.py index c9aa72f1..aa77eb4d 100644 --- a/scripts/docs/pre_pages_content.py +++ b/scripts/docs/pre_pages_content.py @@ -7,6 +7,7 @@ from difflib import SequenceMatcher from hashlib import sha256 from html import unescape +from html.parser import HTMLParser import json from pathlib import Path, PurePosixPath import posixpath @@ -67,11 +68,22 @@ class DocumentPreservation: _IMAGE = re.compile(r'!\[([^\[\]]*)\]\(\s*' + _DESTINATION + _LINK_END) _REFERENCE = re.compile(r"(!?)\[([^\[\]]+)\](?:\[([^\[\]]*)\])?") _DEFINITION = re.compile(r"(?m)^ {0,3}\[([^\]]+)\]:\s*" + _DESTINATION + r"[^\n]*$") -_ATTRIBUTE = re.compile(r'''([\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))''') -_HTML_LINK = re.compile( - r"""])*)>(.*?)""", - re.IGNORECASE | re.DOTALL, +_HTML_TAG = re.compile( + r"""])(?:"[^"]*"|'[^']*'|[^'">])*>""", + re.DOTALL, ) +_HTML_OPEN = re.compile(r"])") +_AUTOLINK = re.compile(r"<((?:https?://|mailto:)[^<>\s]+)>", re.IGNORECASE) +_FENCE_OPEN = re.compile(r"((?:[ \t]{0,3}>[ \t]?)*)([ \t]*)(`{3,}|~{3,})(.*)") +_PRESENTATION_TAGS = frozenset(( + "div", "section", "article", "aside", "nav", "p", "span", "strong", + "em", "b", "i", "a", "ol", "ul", "li", "br", +)) +_RAW_TAGS = frozenset(( + "script", "style", "code", "pre", "textarea", "title", "xmp", "iframe", + "noembed", "noframes", "noscript", "template", "plaintext", +)) +_TEXT_RAW_TAGS = _RAW_TAGS - {"code", "pre", "template"} _WRAPPER = re.compile( r"]*>", re.IGNORECASE, @@ -87,6 +99,166 @@ def _escaped_match(match: re.Match) -> bool: return (len(prefix) - len(prefix.rstrip("\\"))) % 2 == 1 +@dataclass(frozen=True) +class _HtmlFragment: + raw: str + rendered: str + label: str + images: tuple[tuple[str, str], ...] = () + links: tuple[tuple[str, str], ...] = () + wrapper: bool = False + + +class _HtmlElementReader(HTMLParser): + """Read element semantics without interpreting any attribute or text as Markdown.""" + + CDATA_CONTENT_ELEMENTS = tuple(_RAW_TAGS - {"template"}) + + def __init__(self) -> None: + super().__init__(convert_charrefs=True) + self.text: list[str] = [] + self.images: list[tuple[str, str]] = [] + self.href: str | None = None + self.raw_tag: str | None = None + self.template_depth = 0 + + def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: + if self.template_depth: + self.template_depth += tag == "template" + return + if tag == "template": + self.template_depth = 1 + return + if tag in _RAW_TAGS: + self.raw_tag = tag + return + values = {} + for name, value in attrs: + values.setdefault(name, value or "") + if tag == "a" and self.href is None: + self.href = values.get("href") + elif tag == "img": + self.images.append((values.get("alt", ""), values.get("src", ""))) + elif tag in {"br", "hr", "p", "div", "li", "tr"}: + self.text.append(" ") + + def handle_startendtag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: + self.handle_starttag(tag, attrs) + if tag in _RAW_TAGS: + if tag != "template": + self.set_cdata_mode(tag) + return + self.handle_endtag(tag) + + def handle_endtag(self, tag: str) -> None: + if self.template_depth: + self.template_depth -= tag == "template" + elif self.raw_tag == tag: + self.raw_tag = None + elif tag in {"p", "div", "li", "tr"}: + self.text.append(" ") + + def handle_data(self, data: str) -> None: + if not self.template_depth and self.raw_tag not in {"script", "style", "iframe", "noembed", "noframes", "noscript"}: + self.text.append(unescape(data) if self.raw_tag else data) + + +def _html_element_end(text: str, start: int, tag: str) -> int | None: + if tag == "plaintext": + return len(text) + if tag in _TEXT_RAW_TAGS: + closing = re.search(r"", text[start:], re.IGNORECASE) + return start + closing.end() if closing else None + depth = 1 + position = start + while position < len(text): + position = text.find("<", position) + if position < 0: + return None + if text.startswith("", position + 4) + position = len(text) if end < 0 else end + 3 + continue + token = _HTML_TAG.match(text, position) + if token is None: + position += 1 + continue + name = token[1].lower() + closing = token[0].startswith(" tuple[int, _HtmlFragment] | None: + token = _HTML_TAG.match(text, position) + if token is None: + if _HTML_OPEN.match(text, position): + raw = text[position:] + return len(text), _HtmlFragment(raw, raw, raw) + return None + raw, tag, end = token[0], token[1].lower(), token.end() + escaped = _escaped_match(token) + opening = not raw.startswith(" tuple[str, int]: + parts = [] + while position < len(text) and text[position] != "\n": + if text.startswith("", position + 4) + position = len(text) if end < 0 else end + 3 + continue + code = re.match(r"(`+)(.*?)\1", text[position:]) if text[position] == "`" else None + if code: + parts.append(code[0]) + position += code.end() + continue + autolink = _AUTOLINK.match(text, position) + if autolink: + parts.append(autolink[0]) + position = autolink.end() + continue + if text[position] == "<": + fragment = _html_fragment(text, position) + if fragment: + position, value = fragment + key = f"{prefix}{len(html)}\ue101" + html[key] = value + parts.append(key) + continue + parts.append(text[position]) + position += 1 + return "".join(parts), position + (position < len(text)) + + def _unquote(line: str, depth: int) -> str | None: for _ in range(depth): prefix = re.match(r"^[ \t]{0,3}>[ \t]?", line) @@ -96,7 +268,9 @@ def _unquote(line: str, depth: int) -> str | None: return line -def _segments(text: str) -> list[tuple[str | None, str]]: +def _segments( + text: str, html: dict[str, _HtmlFragment] | None = None, +) -> list[tuple[str | None, str]]: """Separate fences before applying any prose-only normalization.""" segments = [] normal: list[str] = [] @@ -105,8 +279,15 @@ def _segments(text: str) -> list[tuple[str | None, str]]: language = "" indent = "" quote_depth = 0 - comment = False - for line in text.split("\n"): + html = html if html is not None else {} + prefix = "\ue100html" + while prefix in text: + prefix += "x" + position = 0 + while position < len(text): + end = text.find("\n", position) + end = len(text) if end < 0 else end + line = text[position:end] if fence: code_line = _unquote(line, quote_depth) if code_line is None: @@ -117,31 +298,18 @@ def _segments(text: str) -> list[tuple[str | None, str]]: ): segments.append((language, "\n".join(code).strip("\n"))) fence, code = "", [] + position = end + 1 continue else: code.append(code_line.removeprefix(indent).rstrip()) + position = end + 1 continue - visible = [] - offset = 0 - while offset < len(line): - if comment: - end = line.find("-->", offset) - if end < 0: - break - offset, comment = end + 3, False - continue - token = re.search(r"(`+).*?\1|") + + +def _rendered_semantics( + text: str, html_spans: list[tuple[int, int]], +) -> tuple[_RenderedSemantics, dict]: + for start, end in reversed(html_spans): + reader = _RendererHtmlBoundary() + reader.feed(text[start:end]) + reader.close() + text = text[:start] + "".join(reader.output) + text[end:] + renderer = markdown.Markdown( + extensions=list(_SEMANTIC_EXTENSIONS), + extension_configs={"pymdownx.highlight": {"use_pygments": False}}, + ) + output = renderer.convert(text) + collector = _RenderedSemantics() + collector.feed(output) + collector.close() + collector._finish_anchor() + return collector, renderer.references + + @dataclass(frozen=True) class _HtmlFragment: raw: str rendered: str label: str - images: tuple[tuple[str, str], ...] = () - links: tuple[tuple[str, str], ...] = () wrapper: bool = False class _HtmlElementReader(HTMLParser): - """Read element semantics without interpreting any attribute or text as Markdown.""" + """Read source prose text; semantic links/images are collected only after rendering.""" CDATA_CONTENT_ELEMENTS = tuple(_RAW_TAGS - {"template"}) def __init__(self) -> None: super().__init__(convert_charrefs=True) self.text: list[str] = [] - self.images: list[tuple[str, str]] = [] - self.href: str | None = None self.raw_tag: str | None = None self.template_depth = 0 @@ -132,14 +329,7 @@ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None if tag in _RAW_TAGS: self.raw_tag = tag return - values = {} - for name, value in attrs: - values.setdefault(name, value or "") - if tag == "a" and self.href is None: - self.href = values.get("href") - elif tag == "img": - self.images.append((values.get("alt", ""), values.get("src", ""))) - elif tag in {"br", "hr", "p", "div", "li", "tr"}: + if tag in {"br", "hr", "p", "div", "li", "tr"}: self.text.append(" ") def handle_startendtag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: @@ -215,20 +405,18 @@ def _html_fragment(text: str, position: int) -> tuple[int, _HtmlFragment] | None reader.close() label = _space("".join(reader.text)) if tag == "a": - links = ((label, reader.href),) if label and reader.href is not None else () - return end, _HtmlFragment(raw, f" {label} ", label, tuple(reader.images), links) + return end, _HtmlFragment(raw, f" {label} ", label) return end, _HtmlFragment(raw, raw, label) if opening and tag == "img" and not escaped: - reader = _HtmlElementReader() - reader.feed(raw) - reader.close() - return end, _HtmlFragment(raw, "", "", tuple(reader.images)) + return end, _HtmlFragment(raw, "", "") wrapper = tag in _PRESENTATION_TAGS return end, _HtmlFragment(raw, " " if wrapper else raw, raw if escaped else "", wrapper=wrapper) def _source_line( text: str, position: int, html: dict[str, _HtmlFragment], prefix: str, + literals: dict[str, str], + html_spans: list[tuple[int, int]], ) -> tuple[str, int]: parts = [] while position < len(text) and text[position] != "\n": @@ -236,10 +424,19 @@ def _source_line( end = text.find("-->", position + 4) position = len(text) if end < 0 else end + 3 continue - code = re.match(r"(`+)(.*?)\1", text[position:]) if text[position] == "`" else None - if code: - parts.append(code[0]) - position += code.end() + if text[position] == "`": + boundary = re.search(r"\n(?:[ \t]*\n| {0,3}#{1,6}[ \t]+| {0,3}(?:`{3,}|~{3,}))", text[position:]) + limit = position + boundary.start() if boundary else len(text) + span = _code_span_at(text, position, limit) + if span: + key = f"{prefix}code{len(literals)}\ue101" + literals[key] = span.value + parts.append(key) + position = span.end + else: + end = _backtick_run_end(text, position) + parts.append(text[position:end]) + position = end continue autolink = _AUTOLINK.match(text, position) if autolink: @@ -249,10 +446,13 @@ def _source_line( if text[position] == "<": fragment = _html_fragment(text, position) if fragment: + start = position position, value = fragment key = f"{prefix}{len(html)}\ue101" html[key] = value parts.append(key) + if not (len(text[:start]) - len(text[:start].rstrip("\\"))) % 2: + html_spans.append((start, position)) continue parts.append(text[position]) position += 1 @@ -270,6 +470,8 @@ def _unquote(line: str, depth: int) -> str | None: def _segments( text: str, html: dict[str, _HtmlFragment] | None = None, + literals: dict[str, str] | None = None, + html_spans: list[tuple[int, int]] | None = None, ) -> list[tuple[str | None, str]]: """Separate fences before applying any prose-only normalization.""" segments = [] @@ -280,6 +482,8 @@ def _segments( indent = "" quote_depth = 0 html = html if html is not None else {} + literals = literals if literals is not None else {} + html_spans = html_spans if html_spans is not None else [] prefix = "\ue100html" while prefix in text: prefix += "x" @@ -305,11 +509,15 @@ def _segments( position = end + 1 continue opening = _FENCE_OPEN.fullmatch(line) + if opening and opening[3].startswith("`") and "`" in opening[4]: + opening = None if opening: position = end + 1 else: - line, position = _source_line(text, position, html, prefix) + line, position = _source_line(text, position, html, prefix, literals, html_spans) opening = _FENCE_OPEN.fullmatch(line) + if opening and opening[3].startswith("`") and "`" in opening[4]: + opening = None if opening: segments.append((None, "\n".join(normal))) normal = [] @@ -328,10 +536,19 @@ def _table_cells(line: str) -> list[str]: line = line.strip() cells = [] start = 0 - for token in re.finditer(r"(?P`+).*?(?P=ticks)|\\.|(?P\|)", line): - if token["bar"]: - cells.append(line[start:token.start()]) - start = token.end() + spans = {span.start: span for span in _code_spans(line)} + position = 0 + while position < len(line): + if position in spans: + position = spans[position].end + continue + if line[position] == "\\": + position += 2 + continue + if line[position] == "|": + cells.append(line[start:position]) + start = position + 1 + position += 1 cells.append(line[start:]) if line.startswith("|") and not cells[0]: cells.pop(0) @@ -352,20 +569,16 @@ def extract_markdown_structure(text: str) -> MarkdownStructure: while literal_prefix in text: literal_prefix += "x" html: dict[str, _HtmlFragment] = {} - segments = _segments(text, html) - definitions = { - _space(match[1]).casefold(): match[2].strip("<>") - for language, body in segments if language is None - for match in _DEFINITION.finditer(body) - } + source_literals: dict[str, str] = {} + html_spans: list[tuple[int, int]] = [] + segments = _segments(text, html, source_literals, html_spans) + semantics, rendered_references = _rendered_semantics(text, html_spans) + definitions = {key: value[0] for key, value in rendered_references.items()} title = "" headings: list[str] = [] prose: list[str] = [] code: list[str] = [] tables: list[str] = [] - images: list[str] = [] - local_link_labels: list[str] = [] - links: list[str] = [] def html_text(value: str, field: str) -> str: for token, fragment in html.items(): @@ -374,40 +587,32 @@ def html_text(value: str, field: str) -> str: def visible(value: str) -> str: value = _WRAPPER.sub(" ", unescape(value)) - value = re.sub(r"(`+)(.*?)\1", r"\2", value) for _ in range(2): value = re.sub(r"(\*\*|\*)(.+?)\1", r"\2", value) value = re.sub(r"(? str: + # Source-only prose normalization; semantic evidence never comes from these regexes. markup = visible(label) - label = _space(html_text(markup, "label")) - target = unescape(html_text(target, "raw").strip("<>")) if image: - images.append(f"{label}\n{PurePosixPath(unquote(urlsplit(target).path)).name}") return "" - if label: - links.append(f"{label}\n{target}") - if label and not re.match(r"^(?:[a-z][\w+.-]*:|//)", target, re.IGNORECASE): - local_link_labels.append(label) return markup def inline(value: str) -> str: literals: dict[str, str] = {} - image_start, label_start, link_start = len(images), len(local_link_labels), len(links) - - def protect(match: re.Match) -> str: - token = f"{literal_prefix}{len(literals)}\ue001" - literals[token] = match[2] - return token def restore(text: str) -> str: for token, literal in literals.items(): text = text.replace(token, literal) + for token, literal in source_literals.items(): + text = text.replace(token, literal) return text - value = re.sub(r"(`+)(.*?)\1", protect, value) + for span in reversed(_code_spans(value)): + token = f"{literal_prefix}{len(literals)}\ue001" + literals[token] = span.value + value = value[:span.start] + token + value[span.end:] def markdown_image(match: re.Match) -> str: return match[0] if _escaped_match(match) else link(match[1], match[2], True) @@ -415,7 +620,7 @@ def markdown_image(match: re.Match) -> str: def reference_image(match: re.Match) -> str: if not match[1] or _escaped_match(match): return match[0] - key = _space(match[3] or match[2]).casefold() + key = _space(match[3] or match[2]).lower() return link(match[2], definitions[key], True) if key in definitions else match[0] value = _IMAGE.sub(markdown_image, value) @@ -434,7 +639,7 @@ def reference(match: re.Match) -> str: escaped = _escaped_match(match) if escaped and not match[1]: return match[0] - key = _space(match[3] or match[2]).casefold() + key = _space(match[3] or match[2]).lower() if key not in definitions: return match[0] if escaped: @@ -443,27 +648,7 @@ def reference(match: re.Match) -> str: value = _REFERENCE.sub(reference, value) - def autolink(match: re.Match) -> str: - if not _escaped_match(match): - link(match[1], match[1], False) - return match[0] - - value = re.sub(r"<((?:https?://|mailto:)[^<>\s]+)>", autolink, value, flags=re.IGNORECASE) - for token, fragment in html.items(): - for _ in range(value.count(token)): - for alt, source in fragment.images: - alt = _space(unicodedata.normalize("NFKC", alt)) - images.append(f"{alt}\n{PurePosixPath(unquote(urlsplit(source).path)).name}") - for label, target in fragment.links: - label = _space(unicodedata.normalize("NFKC", label)) - target = unicodedata.normalize("NFKC", target) - links.append(f"{label}\n{target}") - if not re.match(r"^(?:[a-z][\w+.-]*:|//)", target, re.IGNORECASE): - local_link_labels.append(label) result = _space(html_text(visible(value), "rendered")) - images[image_start:] = [restore(image) for image in images[image_start:]] - local_link_labels[label_start:] = [restore(label) for label in local_link_labels[label_start:]] - links[link_start:] = [restore(value) for value in links[link_start:]] return restore(result) def heading(value: str, level: int) -> None: @@ -516,7 +701,7 @@ def flush() -> None: in_table = True if not _separator(line): tables.append(" | ".join( - inline(cell.replace(r"\|", "|")).replace("\\", "\\\\").replace("|", r"\|") + inline(cell).replace(r"\|", "|").replace("\\", "\\\\").replace("|", r"\|") for cell in _table_cells(line) )) elif re.fullmatch(r"(?:[-*_]\s*){3,}", line): @@ -533,8 +718,8 @@ def flush() -> None: flush() return MarkdownStructure( title, tuple(headings), tuple(prose), tuple(code), tuple(tables), - tuple(images), tuple(local_link_labels), - tuple(links), + tuple(semantics.images), tuple(semantics.local_link_labels), + tuple(semantics.links), ) diff --git a/scripts/docs/pre_pages_inventory.yml b/scripts/docs/pre_pages_inventory.yml index ff91941c..d322fe43 100644 --- a/scripts/docs/pre_pages_inventory.yml +++ b/scripts/docs/pre_pages_inventory.yml @@ -1011,22 +1011,6 @@ documents: path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/sample.yml sha256: 0d354f8901fdd8b0830902a135fc3ebbcda9d41eebf595530eee92845f103d85 link_change: replacement - 64c7be708c8f93ffae9396110852cc8d88ffff643ac70c496405c59ca711debd: - missing_count: 1 - reason: Retained label 'Dockerfile' in docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md, - pointing to docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/Dockerfile. - The current canonical source or owning-sample target was checked; this is a source-link/reader-label - migration, not removal of the referenced content. - evidence: - - kind: structure - path: docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md - category: links - fingerprint: 21b46b9ea9d0c93e23f756d199f30a1c111c1e44536fb0f83665d7a279e939ed - count: 1 - - kind: file - path: docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/Dockerfile - sha256: a6f157d5d5d979095c9f0404c2f8eb7dcc4acf011092b56a9473c0d9e8df48cd - link_change: replacement - baseline_path: aks/pod_db_query_latency.md pages_path: cases/azure-kubernetes-service/pod-database-query-latency/index.md reviewed_changes: {} diff --git a/tests/docs/test_pre_pages_content.py b/tests/docs/test_pre_pages_content.py index 2f4c24d4..05d6cb8a 100644 --- a/tests/docs/test_pre_pages_content.py +++ b/tests/docs/test_pre_pages_content.py @@ -3,12 +3,16 @@ from dataclasses import FrozenInstanceError, replace from collections import Counter from hashlib import sha256 +from html.parser import HTMLParser import json from pathlib import Path, PurePosixPath import re import shutil import subprocess +import unicodedata +from urllib.parse import unquote, urlsplit +import markdown import pytest import yaml @@ -37,6 +41,66 @@ _DEFAULT_REASON = object() +class RendererOracle(HTMLParser): + """Test oracle: collect element semantics from the pinned renderer's HTML.""" + + def __init__(self) -> None: + super().__init__(convert_charrefs=True) + self.parents = [] + self.anchor = None + self.links = [] + self.images = [] + + def handle_starttag(self, tag, attributes): + blocked = any(name in {"script", "style", "code", "pre", "template", "textarea", "title", "xmp", "iframe", "noembed", "noframes", "noscript"} for name in self.parents) + attrs = {} + for name, value in attributes: + attrs.setdefault(name, value or "") + if not blocked and tag == "a" and "href" in attrs: + if self.anchor is not None: + target, parts = self.anchor + self.links.append(f"{self.normal(''.join(parts))}\n{target}") + self.anchor = [attrs["href"], []] + if not blocked and tag == "img" and "src" in attrs: + alt = self.normal(attrs.get("alt", "")) + self.images.append(f"{alt}\n{Path(unquote(urlsplit(attrs['src']).path)).name}") + if self.anchor is not None: + self.anchor[1].append(alt) + if tag not in {"img", "br", "hr", "input", "meta", "link", "source", "wbr"}: + self.parents.append(tag) + + def handle_startendtag(self, tag, attributes): + self.handle_starttag(tag, attributes) + if tag in self.parents: + self.handle_endtag(tag) + + def handle_data(self, data): + if self.anchor is not None and not any(tag in {"script", "style", "template", "iframe", "noembed", "noframes", "noscript"} for tag in self.parents): + self.anchor[1].append(data) + + def handle_endtag(self, tag): + if tag == "a" and self.anchor is not None and not any(name in {"code", "pre", "script", "style", "template"} for name in self.parents): + target, parts = self.anchor + self.links.append(f"{self.normal(''.join(parts))}\n{target}") + self.anchor = None + if tag in self.parents: + del self.parents[len(self.parents) - 1 - self.parents[::-1].index(tag):] + + @staticmethod + def normal(text): + return " ".join(unicodedata.normalize("NFKC", text).split()) + + +def renderer_oracle(source: str) -> RendererOracle: + source = unicodedata.normalize("NFKC", source.replace("\r\n", "\n").replace("\r", "\n")) + source = re.sub(r"\A---[ \t]*\n.*?\n---[ \t]*(?:\n|$)", "", source, count=1, flags=re.DOTALL) + html = markdown.markdown(source, extensions=["tables", "md_in_html", "pymdownx.superfences"]) + collector = RendererOracle() + collector.feed(html) + collector.close() + return collector + + def git(repo: Path, *arguments: str) -> bytes: return subprocess.run( ["git", "-C", str(repo), *arguments], check=True, capture_output=True, @@ -153,14 +217,15 @@ def test_link_evidence_resolves_full_collapsed_and_shortcut_references() -> None def test_link_evidence_excludes_images_comments_and_code_literals() -> None: - structure = extract_markdown_structure( + source = ( "![Image](image.png) [![Logo](logo.svg)](https://example.com)\n\n" "![Reference image][asset]\n\n[asset]: image.svg\n\n" 'HTML image\n\n' "`[Literal](literal.md)`\n\n```md\n[Code](code.md)\n```\n\n" "\n\n[Visible](visible.md)" ) - assert structure.links == ("Visible\nvisible.md",) + structure = extract_markdown_structure(source) + assert structure.links == tuple(renderer_oracle(source).links) def test_link_evidence_keeps_inline_code_label_text_not_placeholder_tokens() -> None: @@ -192,8 +257,8 @@ def test_plain_label_text_is_not_link_evidence() -> None: r"\", r'\Adapter', ]) -def test_escaped_link_markup_does_not_count_as_a_current_hyperlink(text: str) -> None: - assert extract_markdown_structure(text).links == () +def test_escaped_link_markup_follows_the_pinned_renderer(text: str) -> None: + assert extract_markdown_structure(text).links == tuple(renderer_oracle(text).links) def test_even_backslashes_before_a_link_do_not_escape_its_markup() -> None: @@ -1228,7 +1293,7 @@ def test_html_anchor_text_is_not_reinterpreted_as_markdown() -> None: structure = extract_markdown_structure( '**literal**[Text](not-a-link.md)' ) - assert structure.links == ("**literal**[Text](not-a-link.md)\nactual.md",) + assert structure.links == ("literal[Text](not-a-link.md)\nactual.md",) assert structure.images == () @@ -1250,11 +1315,13 @@ def test_html_anchor_and_image_attributes_keep_first_duplicate_values() -> None: assert structure.images == ("First\nfirst.png",) -def test_genuine_html_image_nested_in_anchor_is_not_a_textual_link() -> None: - structure = extract_markdown_structure( +def test_genuine_html_image_alt_text_labels_its_anchor() -> None: + source = ( 'Actual' ) - assert structure.links == () + structure = extract_markdown_structure(source) + expected = renderer_oracle('Actual') + assert structure.links == tuple(expected.links) assert structure.images == ("Actual\nactual.png",) @@ -1278,8 +1345,7 @@ def test_nested_linked_image_escape_parity(slashes: int, image: str) -> None: text = "[" + "\\" * slashes + image + "](target.md)\n\n[asset]: images/picture.png" structure = extract_markdown_structure(text) assert structure.images == (("Alt\npicture.png",) if slashes % 2 == 0 else ()) - if slashes == 0: - assert structure.links == () + assert structure.links == tuple(renderer_oracle(text).links) def test_escaped_image_bang_leaves_an_ordinary_link_not_an_image() -> None: @@ -1297,12 +1363,14 @@ def test_additional_raw_html_containers_do_not_supply_evidence(tag: str) -> None def test_html_tag_tokens_inside_markdown_image_alt_or_destination_do_not_emit_elements() -> None: - structure = extract_markdown_structure( + source = ( '![Outer Fake](outer.png) ' '[Actual]()' ) - assert structure.images == ("Outer\nouter.png",) - assert structure.links == ("Actual\nimg",) + structure = extract_markdown_structure(source) + oracle = renderer_oracle(source) + assert structure.images == tuple(oracle.images) + assert structure.links == tuple(oracle.links) def test_html_anchor_attributes_cannot_forge_a_closing_tag_or_child_image() -> None: @@ -1340,3 +1408,169 @@ def test_self_closing_syntax_on_nonvoid_anchor_children_keeps_actual_rendered_te ) assert structure.links == ("Actual text\nactual.md",) assert structure.images == () + + +@pytest.mark.parametrize("title", [ + "", ' "valid quoted title"', " 'valid single-quoted title'", " (valid parenthesized title)", + " arbitrary unquoted trailing description", +]) +@pytest.mark.parametrize("image", [False, True]) +def test_semantic_reference_definitions_match_the_pinned_renderer(title: str, image: bool) -> None: + marker = "!" if image else "" + source = f"{marker}[Capture][ref]\n\n[ref]: capture.gif{title}" + oracle = renderer_oracle(source) + structure = extract_markdown_structure(source) + assert structure.links == tuple(oracle.links) + assert structure.images == tuple(oracle.images) + expected_local = tuple(link.partition("\n")[0] for link in oracle.links if not urlsplit(link.partition("\n")[2]).scheme) + assert structure.local_link_labels == expected_local + + +@pytest.mark.parametrize("source", [ + '[A](target.md "quoted title")', + "[A](target.md 'single title')", + "[A](target.md (parenthesized title))", + "", + "[![Capture](capture.gif)](details.md)", + '[Before ![Capture](capture.gif) after](details.md)', + 'Before Capture after', + 'Plain', + '[Fake](fake.md) ![Fake](fake.gif)', + '', + '', + r"\![Capture](capture.gif)", + "`[Fake](fake.md) ![Fake](fake.gif)`", + "`literal ``` ![Capture](capture.gif) tail`", +]) +def test_semantic_elements_are_derived_from_renderer_output(source: str) -> None: + oracle = renderer_oracle(source) + structure = extract_markdown_structure(source) + assert structure.links == tuple(oracle.links) + assert structure.images == tuple(oracle.images) + + +@pytest.mark.parametrize(("source", "expected"), [ + ("`literal ``` ![Capture](capture.gif) tail`", "literal ``` ![Capture](capture.gif) tail"), + ("``literal ` one ``` two``", "literal ` one ``` two"), + ("```literal `` short ```` long```", "literal `` short ```` long"), + ("` line one\nline two `", "line one line two"), + ("`` padded ``", " padded "), + ("` `", " "), +]) +def test_source_code_spans_use_exact_delimiter_runs_and_commonmark_whitespace(source: str, expected: str) -> None: + structure = extract_markdown_structure(source) + assert structure.prose == (expected,) + oracle = renderer_oracle(source) + assert structure.images == tuple(oracle.images) + assert structure.links == tuple(oracle.links) + + +@pytest.mark.parametrize("source", [ + "`unmatched ![Capture](capture.gif)", + "``unmatched `short` ![Capture](capture.gif)", + "````unmatched ```short``` ![Capture](capture.gif)", + "````", +]) +def test_unmatched_code_span_runs_follow_renderer_image_visibility(source: str) -> None: + structure = extract_markdown_structure(source) + oracle = renderer_oracle(source) + assert structure.images == tuple(oracle.images) + assert structure.links == tuple(oracle.links) + + +def test_source_code_span_masks_pipes_until_an_exact_closer() -> None: + source = "`literal ```\n| A | B |\n tail`" + structure = extract_markdown_structure(source) + assert structure.title == "" and structure.headings == () and structure.tables == () + assert structure.prose == ("literal ``` | A | B | tail",) + + +def test_unmatched_inline_code_does_not_cross_real_heading_or_paragraph_boundaries() -> None: + source = "`unmatched\n## Heading\n\n![Visible](capture.gif) tail`" + structure = extract_markdown_structure(source) + assert structure.headings == ("Heading",) + assert structure.images == tuple(renderer_oracle(source).images) + + +def test_real_tei_malformed_reference_definition_cannot_replace_the_hyperlink( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, +) -> None: + inventory, path = isolated_real_document_audit( + tmp_path, monkeypatch, "aisearch/custom_vectorization/01_custom_embedding_guide.md", + "docs/services/azure-ai-search/custom-vectorization/index.md", + ) + destination = "https://github.com/hellices/devguidesample/tree/main/docs/services/azure-ai-search/custom-vectorization/samples/implementation/tei-adapter" + old = f"[tei-adapter]({destination})" + source = path.read_text() + assert source.count(old) == 1 + changed = source.replace(old, "[tei-adapter][malformed-tei]") + f"\n\n[malformed-tei]: {destination} arbitrary unquoted description\n" + assert f"tei-adapter\n{destination}" not in renderer_oracle(changed).links + path.write_text(changed) + with pytest.raises(AuditFormatError, match="evidence.*links|links.*count"): + audit_document_content(tmp_path, inventory) + + +@pytest.mark.parametrize("kind", ["image", "link", "code-span"]) +def test_real_s1_inline_parser_mutations_cannot_supply_rendered_evidence( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, kind: str, +) -> None: + inventory, path = isolated_real_document_audit( + tmp_path, monkeypatch, "monitor/sre-agent-event-lab/validation-results.md", + "docs/services/azure-monitor/azure-sre-agent/validation-results/index.md", + ) + source = path.read_text() + image = "![S1 SRE Agent investigation](images/s1-investigation.gif)" + if kind == "image": + changed = source.replace(image, "![S1 SRE Agent investigation][malformed-s1]") + "\n\n[malformed-s1]: images/s1-investigation.gif arbitrary unquoted description\n" + assert "S1 SRE Agent investigation\ns1-investigation.gif" not in renderer_oracle(changed).images + elif kind == "link": + destination = "https://github.com/hellices/devguidesample/blob/main/docs/services/azure-monitor/azure-sre-agent/samples/event-lab/assets/captures/s1/07-conclusion.png" + link = f"[결론 frame]({destination})" + assert source.count(link) == 1 + changed = source.replace(link, "[결론 frame][malformed-s1]") + f"\n\n[malformed-s1]: {destination} arbitrary unquoted description\n" + assert f"결론 frame\n{destination}" not in renderer_oracle(changed).links + else: + assert source.count(image) == 1 + changed = source.replace(image, f"`literal ``` {image} tail`") + assert "S1 SRE Agent investigation\ns1-investigation.gif" not in renderer_oracle(changed).images + path.write_text(changed) + with pytest.raises(AuditFormatError, match="evidence"): + audit_document_content(tmp_path, inventory) + + +def test_unrendered_memray_details_reference_is_not_a_historical_hyperlink_loss() -> None: + inventory = load_inventory(ROOT / "scripts/docs/pre_pages_inventory.yml") + entry = next(d for d in inventory.documents if str(d.baseline_path) == "aks/memray_leak_profiling.md") + baseline = git_text(ROOT, inventory.baseline_commit, entry.baseline_path) + current = (ROOT / "docs/services/azure-kubernetes-service/python-memory-leak-memray/index.md").read_text() + for source in (baseline, current): + oracle = renderer_oracle(source) + assert not any(link.partition("\n")[0] == "Dockerfile" for link in oracle.links) + assert "Dockerfile" not in extract_markdown_structure(source).local_link_labels + assert "[Dockerfile](" in source + assert fingerprint("local_link_labels", "Dockerfile") not in entry.reviewed_changes["local_link_labels"] + payload = PurePosixPath("docs/services/azure-kubernetes-service/python-memory-leak-memray/samples/memray-leak-profiling/Dockerfile") + assert any(ref.path == payload for group in entry.reviewed_changes.values() for change in group.values() for ref in change.evidence) + + +def test_raw_html_attribute_autolinks_do_not_change_a_genuine_anchor() -> None: + source = 'Actual' + expected = renderer_oracle('Actual') + structure = extract_markdown_structure(source) + assert structure.links == tuple(expected.links) + + +def test_malformed_reference_definition_remains_source_prose() -> None: + source = "[Capture][ref]\n\n[ref]: capture.gif arbitrary unquoted description" + structure = extract_markdown_structure(source) + assert structure.prose == ("[Capture][ref]", "[ref]: capture.gif arbitrary unquoted description") + assert structure.links == tuple(renderer_oracle(source).links) + assert structure.images == tuple(renderer_oracle(source).images) + + +def test_table_code_span_escaped_pipe_is_unescaped_once_after_literal_restoration() -> None: + source = "| Command | Result |\n|---|---|\n| `ps aux \\| grep MetricsExtension` | present |" + structure = extract_markdown_structure(source) + assert structure.tables[-1] == r"ps aux \| grep MetricsExtension | present" + assert structure.links == tuple(renderer_oracle(source).links) + assert structure.images == tuple(renderer_oracle(source).images) From 84a5cf8f1c7a58e524cec13ce19c856d30ec92b7 Mon Sep 17 00:00:00 2001 From: hellices Date: Mon, 14 Sep 2026 04:34:45 +0900 Subject: [PATCH 10/26] fix(docs): share repository semantic renderer configuration Enable attribute lists and the safe repository Markdown extensions through one immutable configuration used by production and test oracles. Fingerprint final href/src/alt overrides, document generated-TOC and snippet exclusions, and regress the real S1 override without changing existing approvals. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/docs/pre_pages_content.py | 39 ++++++++++-- tests/docs/test_pre_pages_content.py | 88 +++++++++++++++++++++++++++- 2 files changed, 120 insertions(+), 7 deletions(-) diff --git a/scripts/docs/pre_pages_content.py b/scripts/docs/pre_pages_content.py index 4958804d..d9b877ec 100644 --- a/scripts/docs/pre_pages_content.py +++ b/scripts/docs/pre_pages_content.py @@ -13,6 +13,7 @@ import posixpath import re from typing import Mapping +from types import MappingProxyType import unicodedata from urllib.parse import unquote, urlsplit @@ -91,11 +92,42 @@ class DocumentPreservation: r"]*>", re.IGNORECASE, ) -_SEMANTIC_EXTENSIONS = ("tables", "md_in_html", "pymdownx.superfences") +# Source-semantic subset of mkdocs.yml, shared with renderer-oracle tests: +# attr_list changes final href/src/alt; md_in_html enables nested Markdown. +# tables/admonition/details/tabbed/tasklist expose source content in containers. +# highlight/inlinehilite/superfences identify code that must not supply evidence. +# Coloring is disabled only for speed; generated code markup is ignored either way. +SEMANTIC_MARKDOWN_CONFIG = MappingProxyType({ + "extensions": ( + "admonition", "attr_list", "md_in_html", "tables", "pymdownx.details", + "pymdownx.highlight", "pymdownx.inlinehilite", "pymdownx.superfences", + "pymdownx.tabbed", "pymdownx.tasklist", + ), + "extension_configs": MappingProxyType({ + "pymdownx.highlight": MappingProxyType({"anchor_linenums": True, "use_pygments": False}), + "pymdownx.tabbed": MappingProxyType({"alternate_style": True}), + "pymdownx.tasklist": MappingProxyType({"custom_checkbox": True}), + }), +}) +SEMANTIC_MARKDOWN_EXCLUSIONS = MappingProxyType({ + "toc": "Generated TOC/permalink navigation is not authored evidence; source headings are compared separately.", + "pymdownx.snippets": "File inclusion has side effects; no snippet directives occur in the audited source corpus.", +}) _VOID_TAGS = frozenset(("area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr")) _HIDDEN_TAGS = frozenset(("script", "style", "template", "iframe", "noembed", "noframes", "noscript", "title")) +def create_semantic_renderer() -> markdown.Markdown: + """Build a fresh renderer from the single audited, side-effect-free site configuration.""" + return markdown.Markdown( + extensions=list(SEMANTIC_MARKDOWN_CONFIG["extensions"]), + extension_configs={ + name: dict(options) + for name, options in SEMANTIC_MARKDOWN_CONFIG["extension_configs"].items() + }, + ) + + def _space(text: str) -> str: return " ".join(text.split()) @@ -288,10 +320,7 @@ def _rendered_semantics( reader.feed(text[start:end]) reader.close() text = text[:start] + "".join(reader.output) + text[end:] - renderer = markdown.Markdown( - extensions=list(_SEMANTIC_EXTENSIONS), - extension_configs={"pymdownx.highlight": {"use_pygments": False}}, - ) + renderer = create_semantic_renderer() output = renderer.convert(text) collector = _RenderedSemantics() collector.feed(output) diff --git a/tests/docs/test_pre_pages_content.py b/tests/docs/test_pre_pages_content.py index 05d6cb8a..f2a6ecca 100644 --- a/tests/docs/test_pre_pages_content.py +++ b/tests/docs/test_pre_pages_content.py @@ -12,7 +12,6 @@ import unicodedata from urllib.parse import unquote, urlsplit -import markdown import pytest import yaml @@ -94,7 +93,7 @@ def normal(text): def renderer_oracle(source: str) -> RendererOracle: source = unicodedata.normalize("NFKC", source.replace("\r\n", "\n").replace("\r", "\n")) source = re.sub(r"\A---[ \t]*\n.*?\n---[ \t]*(?:\n|$)", "", source, count=1, flags=re.DOTALL) - html = markdown.markdown(source, extensions=["tables", "md_in_html", "pymdownx.superfences"]) + html = pre_pages_content.create_semantic_renderer().convert(source) collector = RendererOracle() collector.feed(html) collector.close() @@ -1574,3 +1573,88 @@ def test_table_code_span_escaped_pipe_is_unescaped_once_after_literal_restoratio assert structure.tables[-1] == r"ps aux \| grep MetricsExtension | present" assert structure.links == tuple(renderer_oracle(source).links) assert structure.images == tuple(renderer_oracle(source).images) + + +def test_real_s1_src_attribute_override_invalidates_the_existing_image_approval( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, +) -> None: + inventory, path = isolated_real_document_audit( + tmp_path, monkeypatch, "monitor/sre-agent-event-lab/validation-results.md", + "docs/services/azure-monitor/azure-sre-agent/validation-results/index.md", + ) + assert not audit_document_content(tmp_path, inventory)[0].missing + image = "![S1 SRE Agent investigation](images/s1-investigation.gif)" + source = path.read_text() + assert source.count(image) == 1 + changed = source.replace(image, image + '{: src="images/investigation.gif"}') + path.write_text(changed) + expected = "S1 SRE Agent investigation\ninvestigation.gif" + assert expected in renderer_oracle(changed).images + assert expected in extract_markdown_structure(changed).images + with pytest.raises(AuditFormatError, match="stale.*images|evidence.*images|images.*count"): + audit_document_content(tmp_path, inventory) + + +@pytest.mark.parametrize( + ("source", "links", "images"), + [ + ('![S1](images/s1-investigation.gif){: src="images/investigation.gif"}', (), ("S1\ninvestigation.gif",)), + ("[Guide](original.md){href=override.md}", ("Guide\noverride.md",), ()), + ('[Guide](original.md){: href="override.md#part" .button}', ("Guide\noverride.md#part",), ()), + ('![Original](images/original.png){alt="Final alt" .photo}', (), ("Final alt\noriginal.png",)), + ('![Original](images/original.png){: src="images/final.png" alt="Final" .photo}', (), ("Final\nfinal.png",)), + ('![Original](images/original.png){.photo width="400"}', (), ("Original\noriginal.png",)), + ('[Guide](original.md){.button #guide title="Visible title"}', ("Guide\noriginal.md",), ()), + ('[![Original](images/original.png){alt="Final"}](guide.md){href=final.md}', ("Final\nfinal.md",), ("Final\noriginal.png",)), + ], +) +def test_repository_attribute_lists_define_final_element_semantics(source: str, links: tuple, images: tuple) -> None: + structure = extract_markdown_structure(source) + assert structure.links == links + assert structure.images == images + oracle = renderer_oracle(source) + assert structure.links == tuple(oracle.links) + assert structure.images == tuple(oracle.images) + + +def test_raw_html_markdown_enabled_block_uses_nested_link_and_image_attributes() -> None: + source = ( + '
\n\n' + '[Guide](original.md){href=final.md}\n\n' + '![Original](images/original.png){src="images/final.png" alt="Final" .photo}\n\n' + '
' + ) + structure = extract_markdown_structure(source) + assert structure.links == ("Guide\nfinal.md",) + assert structure.images == ("Final\nfinal.png",) + oracle = renderer_oracle(source) + assert structure.links == tuple(oracle.links) + assert structure.images == tuple(oracle.images) + + +def test_semantic_configuration_accounts_for_every_repository_extension() -> None: + configured = yaml.safe_load((ROOT / "mkdocs.yml").read_text())["markdown_extensions"] + site = {} + for entry in configured: + if isinstance(entry, str): + site[entry] = {} + else: + site.update(entry) + config = pre_pages_content.SEMANTIC_MARKDOWN_CONFIG + exclusions = pre_pages_content.SEMANTIC_MARKDOWN_EXCLUSIONS + assert {"attr_list", "md_in_html"} <= set(config["extensions"]) + assert set(config["extensions"]) == set(site) - set(exclusions) + assert set(exclusions) == {"toc", "pymdownx.snippets"} + assert all(exclusions.values()) + for extension in config["extensions"]: + for key, value in site[extension].items(): + assert config["extension_configs"][extension][key] == value + + +def test_safe_semantic_renderer_does_not_execute_snippet_inclusion(tmp_path: Path) -> None: + include = tmp_path / "not-an-audit-source.md" + include.write_text("[Included](should-not-be-read.md)\n") + source = f'--8<-- "{include.as_posix()}"' + assert "pymdownx.snippets" not in pre_pages_content.SEMANTIC_MARKDOWN_CONFIG["extensions"] + assert renderer_oracle(source).links == [] + assert extract_markdown_structure(source).links == () From 9ebda466448de50b4e974fcfbc011e163e1b9472 Mon Sep 17 00:00:00 2001 From: hellices Date: Mon, 14 Sep 2026 05:00:10 +0900 Subject: [PATCH 11/26] test(docs): audit migrated site visibility Audit the fixed pre-Pages corpus through canonical HTML, redirects, search, service/Explore links and rendered assets. Fail closed on malformed output or escaping paths, and preserve atomic JSON diagnostics. Restore Markdown rendering in three MySQL details blocks without changing historical content or approval evidence. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../blue-green-upgrade/index.md | 6 +- scripts/docs/audit_pre_pages.py | 85 +++ scripts/docs/pre_pages.py | 64 ++ scripts/docs/pre_pages_content.py | 5 + scripts/docs/pre_pages_site.py | 439 +++++++++++++ tests/docs/test_pre_pages_site.py | 600 ++++++++++++++++++ 6 files changed, 1196 insertions(+), 3 deletions(-) create mode 100644 scripts/docs/audit_pre_pages.py create mode 100644 scripts/docs/pre_pages_site.py create mode 100644 tests/docs/test_pre_pages_site.py diff --git a/docs/services/azure-database-for-mysql/blue-green-upgrade/index.md b/docs/services/azure-database-for-mysql/blue-green-upgrade/index.md index cc9c4b3d..0a22ca74 100644 --- a/docs/services/azure-database-for-mysql/blue-green-upgrade/index.md +++ b/docs/services/azure-database-for-mysql/blue-green-upgrade/index.md @@ -323,7 +323,7 @@ mysql -h -e " > **Phase 5 (재배포) 불필요** — `DB_HOST=primary.db..internal` 은 CNAME 교체 후에도 동일. -
+
Rollback 절차 보기 > Rollback 가능 조건: new-db에 write가 발생하지 않은 시점(= super_read_only ON 오류로만 발생). new-db에 이미 데이터가 작성된 경우는 데이터 동기화 후 교체해야 하며, 데이터 팀 개입이 필요합니다. @@ -351,7 +351,7 @@ mysql -h -e "SET GLOBAL super_read_only = OFF;" ## 시뮬레이션 (`simulation/`) -
+
시뮬레이션 구성, 설계 결정, 실행 순서 보기 ### 목적 @@ -622,7 +622,7 @@ LIMIT 1; → **사전에 new-db에서 전체 쿼리 regression test 수행 필수** -
+
5. Rollback 절차 커트오버 후 문제 발생 시 아래 순서로 원복합니다. diff --git a/scripts/docs/audit_pre_pages.py b/scripts/docs/audit_pre_pages.py new file mode 100644 index 00000000..9e6e427e --- /dev/null +++ b/scripts/docs/audit_pre_pages.py @@ -0,0 +1,85 @@ +"""Audit preservation and visibility of the fixed pre-Pages public corpus.""" + +from __future__ import annotations + +import argparse +from collections.abc import Mapping +from dataclasses import fields, is_dataclass +import json +from pathlib import Path, PurePath +import sys +from uuid import uuid4 + + +REPO_ROOT = Path(__file__).resolve().parents[2] +if str(REPO_ROOT) not in sys.path: + sys.path.insert(0, str(REPO_ROOT)) + +from scripts.docs.pre_pages import PreservationAuditResult, audit_repository + + +def _json_value(value: object) -> object: + if is_dataclass(value): + return {field.name: _json_value(getattr(value, field.name)) for field in fields(value)} + if isinstance(value, Mapping): + return {str(key): _json_value(item) for key, item in value.items()} + if isinstance(value, (tuple, list)): + return [_json_value(item) for item in value] + if isinstance(value, PurePath): + return value.as_posix() + return value + + +def write_audit_json(path: Path, result: PreservationAuditResult) -> None: + """Replace a report only after the entire new JSON document has been written.""" + path.parent.mkdir(parents=True, exist_ok=True) + sibling = path.with_name(f".{path.name}.{uuid4().hex}.tmp") + try: + with sibling.open("x", encoding="utf-8") as stream: + stream.write(json.dumps(_json_value(result), ensure_ascii=False, indent=2, sort_keys=True) + "\n") + sibling.replace(path) + finally: + sibling.unlink(missing_ok=True) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--repo-root", type=Path, default=REPO_ROOT) + parser.add_argument("--site-dir", type=Path) + parser.add_argument("--inventory", type=Path) + parser.add_argument("--json-output", type=Path) + parser.add_argument("--content-only", action="store_true") + args = parser.parse_args(argv) + root = args.repo_root.resolve() + site = args.site_dir if args.site_dir is not None else Path("site") + inventory = args.inventory if args.inventory is not None else Path("scripts/docs/pre_pages_inventory.yml") + result = audit_repository( + root, root / site, root / inventory, content_only=args.content_only, + ) + if args.json_output: + try: + write_audit_json(args.json_output, result) + except (OSError, ValueError) as error: + print(f"{args.json_output}: cannot write audit JSON: {error}") + return 1 + for warning in result.warnings: + print(f"Warning: {warning}") + if result.errors: + for error in result.errors: + print(error.replace("\r", "\\r").replace("\n", "\\n")) + return 1 + summary = ( + f"Audited {result.baseline_file_count} baseline files and " + f"{result.baseline_markdown_count} Markdown files: " + f"{result.preserved_documents}/{result.document_count} public documents " + ) + print(summary + ( + "mapped and preserved (content-only; built site not inspected)." + if args.content_only else + "mapped, preserved, redirected, searchable, and visible." + )) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/docs/pre_pages.py b/scripts/docs/pre_pages.py index 9cc41bef..10e19aa7 100644 --- a/scripts/docs/pre_pages.py +++ b/scripts/docs/pre_pages.py @@ -114,6 +114,18 @@ class GitFileDisposition: current_paths: tuple[PurePosixPath, ...] +@dataclass(frozen=True) +class PreservationAuditResult: + baseline_file_count: int + baseline_markdown_count: int + document_count: int + preserved_documents: int + reviewed_documents: int + errors: tuple[str, ...] + warnings: tuple[str, ...] + details: Mapping[str, object] + + def _fields(value: object, required: set[str], label: str, optional: set[str] = frozenset()) -> dict: if not isinstance(value, dict): raise AuditFormatError(f"{label} must be a mapping") @@ -563,3 +575,55 @@ def classify_baseline_files( "unreviewed deleted baseline paths: " + ", ".join(str(path) for path in sorted(unreviewed)) ) return tuple(classified[path] for path in paths) + + +def audit_repository( + repo_root: Path, + site_dir: Path, + inventory_path: Path, + *, + content_only: bool = False, +) -> PreservationAuditResult: + """Audit fixed Git lineage, live source preservation, and optionally built output.""" + from scripts.docs.pre_pages_content import audit_document_content + from scripts.docs.pre_pages_site import inspect_built_site + + errors: list[str] = [] + details: dict[str, object] = {"content_only": content_only} + file_count = markdown_count = document_count = preserved = reviewed = 0 + root = repo_root.resolve() + try: + inventory = load_inventory(inventory_path) + paths = baseline_paths(root, inventory) + file_count = len(paths) + markdown_count = sum(path.suffix.casefold() == ".md" for path in paths) + document_count = len(inventory.documents) + if (file_count, markdown_count, document_count) != (359, 73, 62): + raise AuditFormatError( + f"{inventory_path}: expected 359 baseline files, 73 Markdown files, and 62 documents; " + f"found {file_count}, {markdown_count}, and {document_count}" + ) + details["baseline_commit"] = inventory.baseline_commit + details["pages_commit"] = inventory.pages_commit + details["files"] = [asdict(item) for item in classify_baseline_files(root, inventory)] + content = audit_document_content(root, inventory) + details["documents"] = [asdict(item) for item in content] + preserved = sum(item.status != "missing" for item in content) + reviewed = sum(item.status == "reviewed" for item in content) + for item in content: + for finding in item.missing: + errors.append( + f"{item.current_path}: unreviewed missing {finding.category} " + f"{finding.fingerprint} from {item.baseline_path}: {finding.excerpt}" + ) + if not content_only: + catalog = build_topic_catalog(root / "docs", load_taxonomy(root / "docs-taxonomy.yml")) + site_result = inspect_built_site(root, site_dir, inventory, catalog) + errors.extend(site_result.errors) + details["site"] = site_result.details + except (OSError, ValueError, RuntimeError, yaml.YAMLError) as error: + errors.append(str(error)) + return PreservationAuditResult( + file_count, markdown_count, document_count, preserved, reviewed, + tuple(errors), (), MappingProxyType(details), + ) diff --git a/scripts/docs/pre_pages_content.py b/scripts/docs/pre_pages_content.py index d9b877ec..404719dc 100644 --- a/scripts/docs/pre_pages_content.py +++ b/scripts/docs/pre_pages_content.py @@ -438,6 +438,11 @@ def _html_fragment(text: str, position: int) -> tuple[int, _HtmlFragment] | None return end, _HtmlFragment(raw, raw, label) if opening and tag == "img" and not escaped: return end, _HtmlFragment(raw, "", "") + if opening and tag == "details" and not escaped and re.fullmatch( + r"""""", raw, re.IGNORECASE, + ): + # The rendering opt-in is not historical prose; keep raw HTML for link semantics. + return end, _HtmlFragment(raw, "
", "") wrapper = tag in _PRESENTATION_TAGS return end, _HtmlFragment(raw, " " if wrapper else raw, raw if escaped else "", wrapper=wrapper) diff --git a/scripts/docs/pre_pages_site.py b/scripts/docs/pre_pages_site.py new file mode 100644 index 00000000..60170468 --- /dev/null +++ b/scripts/docs/pre_pages_site.py @@ -0,0 +1,439 @@ +"""Fail-closed inspection of rendered pre-Pages destinations and local assets.""" + +from __future__ import annotations + +from collections import Counter +from dataclasses import dataclass +from html.parser import HTMLParser +import json +from pathlib import Path, PurePosixPath +import re +from typing import Mapping +from urllib.parse import unquote, urlsplit + +import yaml + +from scripts.docs.pre_pages import AuditFormatError, PrePagesInventory +from scripts.docs.topics import TopicCatalog + + +@dataclass(frozen=True) +class SiteInspection: + errors: tuple[str, ...] + details: Mapping[str, object] + + +_VOID = frozenset(( + "area", "base", "br", "col", "embed", "hr", "img", "input", "link", + "meta", "param", "source", "track", "wbr", +)) +_INERT = frozenset(("script", "style", "template", "noscript")) +_CHROME = frozenset(("nav", "header", "footer", "aside")) + + +def _srcset(value: str) -> list[str]: + """Read URL tokens without splitting embedded commas in data URLs.""" + urls = [] + remaining = value.strip() + if not remaining: + raise AuditFormatError("empty srcset") + while remaining: + match = re.match(r"(\S+)(.*)", remaining, re.DOTALL) + assert match is not None + token, remaining = match.groups() + if token.endswith(","): + token = token.rstrip(",") + else: + descriptors, _, remaining = remaining.partition(",") + descriptors = descriptors.split() + if len(descriptors) > 1 or any( + not re.fullmatch(r"(?:[1-9]\d*w|(?:\d+(?:\.\d+)?|\.\d+)x)", item) + or float(item[:-1]) <= 0 + for item in descriptors + ): + raise AuditFormatError(f"malformed srcset descriptors: {descriptors}") + if not token: + raise AuditFormatError("empty srcset URL") + urls.append(token) + remaining = remaining.strip() + return urls + + +class _Page(HTMLParser): + def __init__(self) -> None: + super().__init__(convert_charrefs=True) + self.stack: list[tuple[str, bool]] = [] + self.seen: Counter[str] = Counter() + self.links: list[str] = [] + self.targets: list[tuple[str, str]] = [] + self.canonicals: list[str] = [] + self.refreshes: list[str] = [] + self.errors: list[str] = [] + self.anchor: tuple[str, bool] | None = None + self.source_lines: list[str] = [] + + def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: + attributes = dict(attrs) + if len(attributes) != len(attrs): + self.errors.append(f"duplicate attributes on <{tag}>") + parents = {name for name, _ in self.stack} + inert = bool(parents & _INERT) + if "aria-hidden" in attributes and attributes["aria-hidden"] not in {"true", "false"}: + self.errors.append(f"invalid aria-hidden attribute on <{tag}>") + hidden = ( + any(hidden for _, hidden in self.stack) + or "hidden" in attributes + or (attributes.get("aria-hidden") or "").casefold() == "true" + or bool(re.search( + r"(?:display\s*:\s*none|visibility\s*:\s*hidden)", + attributes.get("style") or "", re.IGNORECASE, + )) + ) + if tag in {"html", "head", "body"}: + if ( + tag == "html" and self.stack + or tag in {"head", "body"} and [name for name, _ in self.stack] != ["html"] + or tag == "body" and self.seen["head"] != 1 + ): + self.errors.append(f"misplaced <{tag}> element") + self.seen[tag] += 1 + if tag == "base": + self.errors.append(" would change local URL resolution") + if not inert: + if tag in {"img", "source"}: + for attr in ("src", "srcset"): + if attr not in attributes: + continue + value = attributes[attr] or "" + try: + targets = _srcset(value) if attr == "srcset" else [value] + for target in targets: + if not target: + raise AuditFormatError(f"empty {tag} {attr}") + self.targets.append((f"{tag} {attr}", target)) + except AuditFormatError as error: + self.errors.append(str(error)) + if tag == "img" and not {"src", "srcset"} & attributes.keys(): + self.errors.append(" has neither src nor srcset") + if tag == "a" and "href" in attributes: + if "a" in parents: + self.errors.append("nested anchor elements") + target = attributes["href"] or "" + self.targets.append(("a href", target)) + if not hidden and not parents & _CHROME and "body" in parents: + self.anchor = (target, False) + if tag in {"img", "svg"} and self.anchor is not None and not hidden: + self.anchor = (self.anchor[0], True) + if tag == "link" and "canonical" in (attributes.get("rel") or "").casefold().split(): + if "head" not in parents: + self.errors.append("canonical link is outside ") + self.canonicals.append(attributes.get("href") or "") + if tag == "meta" and (attributes.get("http-equiv") or "").casefold() == "refresh": + if "head" not in parents: + self.errors.append("refresh metadata is outside ") + self.refreshes.append(attributes.get("content") or "") + if tag not in _VOID: + self.stack.append((tag, hidden)) + + def handle_startendtag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: + if tag not in _VOID | {"svg", "math"} and not {name for name, _ in self.stack} & {"svg", "math"}: + self.errors.append(f"self-closing non-void HTML element <{tag}/>") + self.handle_starttag(tag, attrs) + if tag not in _VOID: + self.handle_endtag(tag) + + def handle_endtag(self, tag: str) -> None: + if tag == "a" and self.anchor is not None: + target, visible = self.anchor + if visible: + self.links.append(target) + self.anchor = None + if not self.stack or self.stack[-1][0] != tag: + self.errors.append(f"unmatched closing ") + else: + self.stack.pop() + + def handle_data(self, data: str) -> None: + if ( + self.anchor is not None and data.strip() + and not any(hidden or tag in _INERT for tag, hidden in self.stack) + ): + self.anchor = (self.anchor[0], True) + + def unknown_decl(self, data: str) -> None: + self.errors.append(f"unknown HTML declaration: {data}") + + def handle_comment(self, data: str) -> None: + line, column = self.getpos() + if not self.source_lines[line - 1][column:].startswith("\n" + write(root, baseline, "# Document\n\nPreserved prose.\n" + marker) + entries.append((baseline, old, canonical, marker)) for i in range(11): write(root, f"notes/{i}.md", "Unchanged ancillary Markdown\n") for i in range(286): @@ -478,13 +479,14 @@ def historical_repo(tmp_path_factory): git(root, "add", ".") git(root, "-c", "commit.gpgsign=false", "commit", "-qm", "Baseline") baseline_commit = git(root, "rev-parse", "HEAD") - for baseline, old, canonical in entries: - write(root, "docs/" + old, source(canonical, old)) + for baseline, old, canonical, marker in entries: + write(root, "docs/" + old, source(canonical, old) + marker) + (root / baseline).unlink() git(root, "add", ".") git(root, "-c", "commit.gpgsign=false", "commit", "-qm", "Pages") pages_commit = git(root, "rev-parse", "HEAD") - for baseline, old, canonical in entries: - write(root, "docs/" + canonical, source(canonical, old)) + for baseline, old, canonical, marker in entries: + write(root, "docs/" + canonical, source(canonical, old) + marker) write(root, "site/" + canonical.replace(".md", ".html"), html()) write(root, "site/" + old.replace(".md", ".html"), redirect("../../../" + str(PurePosixPath(canonical).parent) + "/")) @@ -496,12 +498,12 @@ def historical_repo(tmp_path_factory): f'Topic' for i in range(62) ))) write(root, "site/search/search_index.json", json.dumps({ - "docs": [search_entry(canonical) for _, _, canonical in entries], + "docs": [search_entry(canonical) for _, _, canonical, _ in entries], })) write(root, "scripts/docs/pre_pages_inventory.yml", yaml.safe_dump({ "version": 3, "baseline_commit": baseline_commit, "pages_commit": pages_commit, "rename_similarity": 20, "dispositions": {}, - "documents": [{"baseline_path": b, "pages_path": p} for b, p, _ in entries], + "documents": [{"baseline_path": b, "pages_path": p} for b, p, _, _ in entries], })) git(root, "add", ".") git(root, "-c", "commit.gpgsign=false", "commit", "-qm", "Current") @@ -1785,3 +1787,67 @@ def test_html_entity_decoding_for_scheme_checks_happens_only_once(site_repo): write(root, "site/&", "Literal local URL before its fragment") write(root, "site/index.html", html('Literal reference')) assert inspect(site_repo).errors == () + + +@pytest.mark.parametrize("opened", [False, True]) +def test_built_details_without_authored_summary_only_count_when_open(site_repo, opened): + root, _ = site_repo + article = ARTICLE.replace("

", f'

').replace( + "", "

", + ) + write(root, "site/" + ENTRY.replace(".md", ".html"), html().replace(ARTICLE, article)) + errors = inspect(site_repo).errors + assert errors == () if opened else any("authored" in error for error in errors) + + +@pytest.mark.parametrize("channel", ["service", "explore", "redirect"]) +@pytest.mark.parametrize("late_summary", [False, True]) +def test_generated_page_disclosures_require_a_summary_but_allow_a_late_one(site_repo, channel, late_summary): + root, _ = site_repo + summary = "

before

Late" if late_summary else "" + if channel == "service": + path, target = "services/service/index.html", "topic/" + body = f'
{summary}Topic
' + content = html(body) + elif channel == "explore": + path, target = "explore/index.html", "../services/service/topic/" + body = f'
{summary}Topic
' + content = html(body + 'Child') + else: + path, target = OLD.replace(".md", ".html"), "../../../services/service/topic/" + anchor = f'Moved document' + content = redirect(target).replace(anchor, f"
{summary}{anchor}
") + write(root, "site/" + path, content) + errors = inspect(site_repo).errors + if late_summary: + assert errors == () + else: + assert any("not linked" in error or "fallback" in error for error in errors) + + +def test_summaryless_details_still_validate_fetched_assets(site_repo): + root, _ = site_repo + write(root, "site/index.html", html('
')) + assert any("index.html" in error and "missing-summaryless.png" in error for error in inspect(site_repo).errors) + + +IMPLIED_END_ORACLE = json.loads((ROOT / "tests/docs/fixtures/pre_pages_implied_end_chromium.json").read_text()) + + +@pytest.mark.parametrize("case", IMPLIED_END_ORACLE["cases"], ids=lambda case: case["id"]) +def test_built_optional_end_tags_match_chromium_visibility(site_repo, case): + from scripts.docs.pre_pages_content import create_semantic_renderer + + root, _ = site_repo + source_path = root / "docs" / ENTRY + metadata = source_path.read_text().split("\n---\n", 1)[0] + body = "# Document\n\n" + case["html"] + source_path.write_text(metadata + "\n---\n\n" + body) + rendered = create_semantic_renderer().convert(body) + article = '
' + rendered + "
" + write(root, "site/" + ENTRY.replace(".md", ".html"), html().replace(ARTICLE, article)) + errors = inspect(site_repo).errors + if case["visible"]: + assert errors == () + else: + assert any("authored" in error for error in errors) From d67ac0656700c404fecbaa0a4f45dafb1d9560d7 Mon Sep 17 00:00:00 2001 From: hellices Date: Mon, 14 Sep 2026 13:48:30 +0900 Subject: [PATCH 25/26] fix(docs): reject foster ambiguity and isolate raw content Fail closed on unsupported table flow and text, and keep iframe fallback and raw/RCDATA markup out of parent-page evidence and asset discovery. Preserve parsing after proper closes and plaintext-to-EOF behavior. Record Chromium request/currentSrc evidence for comma-containing srcset URLs and retain the existing browser-correct tokenization and visibility inheritance semantics. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/docs/pre_pages_html.py | 38 ++++++++++ scripts/docs/pre_pages_site.py | 20 +++-- scripts/docs/pre_pages_visibility.py | 22 ++++-- .../fixtures/pre_pages_fourth_chromium.json | 49 ++++++++++++ tests/docs/test_pre_pages_content.py | 21 +++++ tests/docs/test_pre_pages_html.py | 64 ++++++++++++++++ tests/docs/test_pre_pages_site.py | 76 +++++++++++++++++++ 7 files changed, 278 insertions(+), 12 deletions(-) create mode 100644 tests/docs/fixtures/pre_pages_fourth_chromium.json diff --git a/scripts/docs/pre_pages_html.py b/scripts/docs/pre_pages_html.py index dd2b8e34..82be567f 100644 --- a/scripts/docs/pre_pages_html.py +++ b/scripts/docs/pre_pages_html.py @@ -1,6 +1,7 @@ """Bounded HTML implied-end rules shared by source and built-page readers.""" from collections.abc import Sequence +from html.parser import HTMLParser from scripts.docs.pre_pages import AuditFormatError @@ -26,6 +27,41 @@ "select": {"option", "optgroup"}, "optgroup": {"option"}, } +_TABLE_ALLOWED = _TABLE_CONTENT - {"p"} | {"caption", "colgroup", "col", "script", "style", "template"} + + +class AuditHTMLParser(HTMLParser): + """Pin parent-page raw/RCDATA rules, including ignored non-void slashes.""" + + CDATA_CONTENT_ELEMENTS = ("script", "style", "xmp", "iframe", "noembed", "noframes") + RCDATA_CONTENT_ELEMENTS = ("textarea", "title") + + def __init__(self, *, convert_charrefs: bool = True) -> None: + super().__init__(convert_charrefs=convert_charrefs) + self.scripting = True + + def enter_raw_content(self, tag: str) -> bool: + if tag in self.CDATA_CONTENT_ELEMENTS or tag in {"plaintext", "noscript"}: + self.set_cdata_mode(tag, escapable=False) + return True + if tag in self.RCDATA_CONTENT_ELEMENTS: + self.set_cdata_mode(tag, escapable=True) + return True + return False + + +def table_foster_context(stack: Sequence[str]) -> bool: + for tag in reversed(stack): + if tag in {"td", "th", "caption", "template", "script", "style", "svg", "math", "foreignobject"}: + return False + if tag in {"table", "tr"} | _TABLE_GROUPS: + return True + return False + + +def validate_table_text(stack: Sequence[str], text: str) -> None: + if text.strip(" \t\n\r\f") and table_foster_context(stack): + raise AuditFormatError("unsupported table foster-parenting text") def _in_scope(stack: Sequence[str], targets: set[str] | frozenset[str], boundaries: set[str] | frozenset[str]) -> int | None: @@ -42,6 +78,8 @@ def open_p_in_scope(stack: Sequence[str]) -> bool: def implied_end_on_start(stack: Sequence[str], tag: str) -> int | None: + if table_foster_context(stack) and tag not in _TABLE_ALLOWED: + raise AuditFormatError(f"unsupported table foster-parenting flow content: <{tag}>") index = None if tag in _TABLE_CONTENT - {"p"}: table = _in_scope(stack, {"table"}, {"template", "svg", "math"}) diff --git a/scripts/docs/pre_pages_site.py b/scripts/docs/pre_pages_site.py index 8a3c010b..87e30a98 100644 --- a/scripts/docs/pre_pages_site.py +++ b/scripts/docs/pre_pages_site.py @@ -4,7 +4,6 @@ from collections import Counter from dataclasses import dataclass -from html.parser import HTMLParser from ipaddress import IPv4Address, IPv6Address import json import math @@ -18,7 +17,9 @@ from scripts.docs.pre_pages import AuditFormatError, PrePagesInventory from scripts.docs.pre_pages_content import create_semantic_renderer -from scripts.docs.pre_pages_html import implied_end_on_end, implied_end_on_start, open_p_in_scope +from scripts.docs.pre_pages_html import ( + AuditHTMLParser, implied_end_on_end, implied_end_on_start, open_p_in_scope, validate_table_text, +) from scripts.docs.pre_pages_visibility import AuthoredContent from scripts.docs.topics import TopicCatalog @@ -33,7 +34,7 @@ class SiteInspection: "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr", )) -_INERT = frozenset(("script", "style", "template", "noscript")) +_INERT = frozenset(("script", "style", "template", "noscript", "iframe", "noembed", "noframes")) _HTML_WHITESPACE = " \t\n\f\r" _URL_C0_SPACE = "".join(chr(codepoint) for codepoint in range(0x21)) _URL_REMOVED_CONTROLS = str.maketrans("", "", "\t\n\r") @@ -51,7 +52,7 @@ def _valid_srcset_descriptor(value: str) -> bool: def _srcset(value: str) -> list[str]: - """Read URL tokens without splitting embedded commas in data URLs.""" + """Read whole URL tokens; a comma without ASCII whitespace stays in the URL.""" urls = [] remaining = value.strip(_HTML_WHITESPACE) if not remaining: @@ -87,7 +88,7 @@ def _refresh_url(value: str) -> str | None: return next(group for group in match.groups() if group is not None) if match else None -class _Page(HTMLParser): +class _Page(AuditHTMLParser): def __init__(self) -> None: super().__init__(convert_charrefs=True) self.stack: list[str] = [] @@ -127,6 +128,8 @@ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None resource = None if tag == "script" and "src" in attributes: resource = ("script src", attributes["src"] or "") + elif tag == "iframe" and attributes.get("src"): + resource = ("iframe src", attributes["src"]) elif tag == "link": relations = set((attributes.get("rel") or "").casefold().split()) destination = (attributes.get("as") or "").casefold() @@ -176,9 +179,16 @@ def handle_startendtag(self, tag: str, attrs: list[tuple[str, str | None]]) -> N if tag not in _VOID | {"svg", "math"} and not set(self.stack) & {"svg", "math"}: self.errors.append(f"self-closing non-void HTML element <{tag}/>") self.handle_starttag(tag, attrs) + foreign = next((name for name in reversed(self.stack) if name in {"svg", "math", "foreignobject"}), None) + if foreign not in {"svg", "math"} and self.enter_raw_content(tag): + return if tag not in _VOID: self.handle_endtag(tag) + def handle_data(self, data: str) -> None: + if self.cdata_elem is None: + validate_table_text(self.stack, data) + def handle_endtag(self, tag: str) -> None: if tag == "p" and not open_p_in_scope(self.stack): return diff --git a/scripts/docs/pre_pages_visibility.py b/scripts/docs/pre_pages_visibility.py index 6062b431..56b2e9f9 100644 --- a/scripts/docs/pre_pages_visibility.py +++ b/scripts/docs/pre_pages_visibility.py @@ -4,18 +4,19 @@ from collections import Counter from dataclasses import dataclass, field -from html.parser import HTMLParser import unicodedata from scripts.docs.pre_pages import AuditFormatError from scripts.docs.pre_pages_css import inline_visibility, presentation_visibility -from scripts.docs.pre_pages_html import implied_end_on_end, implied_end_on_start, open_p_in_scope +from scripts.docs.pre_pages_html import ( + AuditHTMLParser, implied_end_on_end, implied_end_on_start, open_p_in_scope, validate_table_text, +) _VOID = frozenset(( "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr", )) -_NONCONTENT = frozenset(("head", "script", "style", "template", "noscript", "title")) +_NONCONTENT = frozenset(("head", "script", "style", "template", "noscript", "title", "iframe", "noembed", "noframes")) _CHROME = frozenset(("nav", "header", "footer", "aside")) _CHROME_CLASSES = frozenset(( "headerlink", "md-nav", "md-sidebar", "md-search", "md-content__button", "linenos", @@ -37,9 +38,14 @@ def descend(self, attributes: dict[str, str | None], *, svg: bool = False) -> Vi style = presentation_visibility(attributes) if svg else {} style.update(inline_visibility(attributes.get("style") or "")) visibility = style.get("visibility") + visibility_hidden = self.visibility_hidden + if visibility in {"hidden", "collapse"}: + visibility_hidden = True + elif visibility in {"visible", "initial"}: + visibility_hidden = False return Visibility( self.display_hidden or "hidden" in attributes or style.get("display") == "none", - visibility in {"hidden", "collapse"} if visibility in {"visible", "hidden", "collapse", "initial"} else self.visibility_hidden, + visibility_hidden, self.aria_hidden or (attributes.get("aria-hidden") or "").casefold() == "true", self.inert or "inert" in attributes, ) @@ -78,7 +84,7 @@ class _SemanticEvent: text: str = "" -class AuthoredContent(HTMLParser): +class AuthoredContent(AuditHTMLParser): """Resolve the complete tree before collecting authored blocks or reachable links.""" def __init__(self, *, material_article: bool = False) -> None: @@ -120,10 +126,12 @@ def handle_startendtag(self, tag: str, attrs: list[tuple[str, str | None]]) -> N self.stack[-1].svg or any(parent.tag == "math" for parent in self.stack) ): self.handle_endtag(tag) - elif tag in self.CDATA_CONTENT_ELEMENTS: - self.set_cdata_mode(tag) + else: + self.enter_raw_content(tag) def handle_data(self, data: str) -> None: + if self.cdata_elem is None: + validate_table_text([element.tag for element in self.stack], data) self.stack[-1].children.append(data) self.semantic_events.append(_SemanticEvent("data", self.stack[-1], data)) diff --git a/tests/docs/fixtures/pre_pages_fourth_chromium.json b/tests/docs/fixtures/pre_pages_fourth_chromium.json new file mode 100644 index 00000000..1e7a1b2b --- /dev/null +++ b/tests/docs/fixtures/pre_pages_fourth_chromium.json @@ -0,0 +1,49 @@ +{ + "engine": "HeadlessChrome/153.0.0.0", + "captured_at": "2026-09-14", + "base_url": "http://127.0.0.1:54663/", + "srcset": [ + { + "id": "combined", + "value": "images/a.png,b.png", + "candidates": ["images/a.png,b.png"], + "requested": ["http://127.0.0.1:54663/images/a.png,b.png"], + "current_src": "http://127.0.0.1:54663/images/a.png,b.png", + "natural_width": 1 + }, + { + "id": "spaced", + "value": "images/a.png, images/b.png", + "candidates": ["images/a.png", "images/b.png"], + "requested": ["http://127.0.0.1:54663/images/a.png"], + "current_src": "http://127.0.0.1:54663/images/a.png", + "natural_width": 1 + }, + { + "id": "density", + "value": "images/a.png 1x, images/b.png 2x", + "candidates": ["images/a.png", "images/b.png"], + "requested": [], + "current_src": "http://127.0.0.1:54663/images/a.png", + "natural_width": 1, + "note": "The previously loaded a.png was reused." + } + ], + "css": [ + {"id": "inherit", "style": "visibility:inherit", "visibility": "hidden", "visible": false}, + {"id": "unset", "style": "visibility:unset", "visibility": "hidden", "visible": false}, + {"id": "visible", "style": "visibility:visible", "visibility": "visible", "visible": true}, + {"id": "initial", "style": "visibility:initial", "visibility": "visible", "visible": true}, + {"id": "important-inherit", "style": "visibility:visible;visibility:inherit!important", "visibility": "hidden", "visible": false}, + {"id": "important-first", "style": "visibility:inherit!important;visibility:visible", "visibility": "hidden", "visible": false}, + {"id": "important-visible", "style": "visibility:inherit;visibility:visible!important", "visibility": "visible", "visible": true} + ], + "raw": { + "textarea": {"fake_elements": false, "after_closing": true}, + "xmp": {"fake_elements": false, "after_closing": true}, + "iframe": {"fake_elements": false, "after_closing": true}, + "noembed": {"fake_elements": false, "after_closing": true}, + "noframes": {"fake_elements": false, "after_closing": true}, + "plaintext": {"fake_elements": false, "after_closing": false} + } +} diff --git a/tests/docs/test_pre_pages_content.py b/tests/docs/test_pre_pages_content.py index e3f880bc..6b3f88ab 100644 --- a/tests/docs/test_pre_pages_content.py +++ b/tests/docs/test_pre_pages_content.py @@ -2003,3 +2003,24 @@ def test_source_late_first_summary_element_is_openable(): ]) def test_source_optional_end_tags_do_not_extend_hidden_ancestors(html): assert extract_markdown_structure(html).prose + + +@pytest.mark.parametrize("source", [ + "

text

", + "
text
", + "text
", +]) +def test_source_table_foster_ambiguity_is_an_explicit_error(source): + with pytest.raises(AuditFormatError, match="foster"): + extract_markdown_structure(source) + + +@pytest.mark.parametrize("tag", ["textarea", "xmp", "iframe", "noembed", "noframes", "plaintext"]) +def test_source_raw_container_trailing_markup_cannot_be_promoted(tag): + source = ( + f'<{tag}/>FakeFake' + 'After' + ) + structure = extract_markdown_structure(source) + assert structure.images == () + assert structure.links == (() if tag == "plaintext" else ("After\nafter.md",)) diff --git a/tests/docs/test_pre_pages_html.py b/tests/docs/test_pre_pages_html.py index ca509ca8..4355e004 100644 --- a/tests/docs/test_pre_pages_html.py +++ b/tests/docs/test_pre_pages_html.py @@ -5,9 +5,11 @@ from scripts.docs.pre_pages import AuditFormatError from scripts.docs.pre_pages_visibility import AuthoredContent +from scripts.docs.pre_pages_visibility import Visibility ORACLE = json.loads((Path(__file__).parent / "fixtures/pre_pages_implied_end_chromium.json").read_text()) +FOURTH_ORACLE = json.loads((Path(__file__).parent / "fixtures/pre_pages_fourth_chromium.json").read_text()) @pytest.mark.parametrize("case", ORACLE["cases"], ids=lambda case: case["id"]) @@ -49,3 +51,65 @@ def test_unsupported_ambiguous_optional_end_recovery_fails_closed(body): with pytest.raises(AuditFormatError, match="unsupported|ambiguous"): reader.feed(body) reader.close() + + +@pytest.mark.parametrize("container", [ + "{body}
", "{body}
", + "{body}
", +]) +@pytest.mark.parametrize("body", ["

text

", "
text
", "text", " ", 'text']) +def test_table_foster_parenting_flow_or_text_fails_closed(container, body): + reader = AuthoredContent() + with pytest.raises(AuditFormatError, match="foster"): + reader.feed(container.format(body=body)) + reader.close() + + +@pytest.mark.parametrize("container", [ + "
{body}
", + "
{body}
", + "
{body}
", +]) +def test_table_cells_and_captions_allow_normal_flow_content(container): + reader = AuthoredContent() + reader.feed(container.format(body="

public

")) + reader.close() + assert reader.blocks[("p", "public")] == 1 + + +@pytest.mark.parametrize("case", FOURTH_ORACLE["css"], ids=lambda case: case["id"]) +def test_css_inheritance_and_restoration_match_chromium(case): + parent = Visibility().descend({"style": "visibility:hidden!important"}) + child = parent.descend({"style": case["style"]}) + assert child.visibility_hidden is not case["visible"] + + +@pytest.mark.parametrize("tag", FOURTH_ORACLE["raw"]) +@pytest.mark.parametrize("slash", ["", "/"]) +def test_raw_and_rcdata_markup_never_creates_parent_elements(tag, slash): + reader = AuthoredContent() + reader.feed( + f'

Before<{tag}{slash}>Fake' + f'

Fake heading

After

' + ) + reader.close() + assert reader.blocks[("h1", "Fake heading")] == 0 + assert reader.blocks[("img", "")] == 0 + assert reader.links == (["after.html"] if FOURTH_ORACLE["raw"][tag]["after_closing"] else []) + + +@pytest.mark.parametrize("tag", ["iframe", "noembed", "noframes"]) +def test_iframe_and_fallback_text_are_not_parent_authored_content(tag): + reader = AuthoredContent() + reader.feed(f"

Before<{tag}>Fallback markup and textAfter

") + reader.close() + assert reader.blocks[("p", "BeforeAfter")] == 1 + assert not any("Fallback" in text for _, text in reader.blocks) + + +def test_textarea_rcdata_decodes_entities_without_parsing_the_result(): + reader = AuthoredContent() + reader.feed('

After

') + reader.close() + assert reader.blocks[("p", '& literalAfter')] == 1 + assert reader.blocks[("img", "")] == 0 diff --git a/tests/docs/test_pre_pages_site.py b/tests/docs/test_pre_pages_site.py index 86886178..8ccf2021 100644 --- a/tests/docs/test_pre_pages_site.py +++ b/tests/docs/test_pre_pages_site.py @@ -1851,3 +1851,79 @@ def test_built_optional_end_tags_match_chromium_visibility(site_repo, case): assert errors == () else: assert any("authored" in error for error in errors) + + +@pytest.mark.parametrize("body", [ + "

text

", + "
text
", + "text
", + " 
", +]) +def test_built_table_foster_ambiguity_reports_the_source_path(site_repo, body): + root, _ = site_repo + write(root, "site/index.html", html(body)) + assert any("index.html" in error and "foster" in error for error in inspect(site_repo).errors) + + +@pytest.mark.parametrize("tag", ["textarea", "xmp", "iframe", "noembed", "noframes"]) +def test_parent_page_ignores_raw_fallback_assets_but_checks_following_content(site_repo, tag): + root, _ = site_repo + write(root, "site/real.png", b"real") + body = f'<{tag}>Fake' + write(root, "site/index.html", html(body)) + assert inspect(site_repo).errors == () + (root / "site/real.png").unlink() + errors = findings(site_repo) + assert "real.png" in errors and "fake.png" not in errors and "fake.html" not in errors + + +def test_iframe_src_is_checked_without_parsing_its_fallback(site_repo): + root, _ = site_repo + write(root, "site/index.html", html('')) + errors = findings(site_repo) + assert "missing-frame.html" in errors and "fake.png" not in errors + + +@pytest.mark.parametrize("slash", ["", "/"]) +def test_plaintext_to_eof_never_promotes_parent_page_targets(site_repo, slash): + from scripts.docs.pre_pages_site import _Page + + reader = _Page() + reader.finish(html(f'LiteralFake')) + assert not any(raw in {"fake.png", "fake.html"} for _, raw in reader.targets) + assert reader.errors + + +FOURTH_ORACLE = json.loads((ROOT / "tests/docs/fixtures/pre_pages_fourth_chromium.json").read_text()) + + +@pytest.mark.parametrize("case", FOURTH_ORACLE["srcset"], ids=lambda case: case["id"]) +def test_srcset_url_tokens_match_the_permanent_chromium_oracle(case): + from scripts.docs.pre_pages_site import _srcset + + assert _srcset(case["value"]) == case["candidates"] + if case["id"] == "combined": + assert case["requested"] == [case["current_src"]] + assert case["current_src"] == FOURTH_ORACLE["base_url"] + "images/a.png,b.png" + assert case["natural_width"] == 1 + + +def test_comma_without_ascii_whitespace_requires_the_combined_local_asset(site_repo): + root, _ = site_repo + write(root, "site/images/a.png", b"a") + write(root, "site/images/b.png", b"b") + write(root, "site/index.html", html('')) + assert "images/a.png,b.png" in findings(site_repo) + write(root, "site/images/a.png,b.png", b"combined") + assert inspect(site_repo).errors == () + + +@pytest.mark.parametrize("case", FOURTH_ORACLE["css"], ids=lambda case: case["id"]) +def test_built_visibility_inherit_unset_and_restore_controls(site_repo, case): + root, _ = site_repo + article = ARTICLE.replace( + "

Document

", f'

Document

', + ) + write(root, "site/" + ENTRY.replace(".md", ".html"), html().replace(ARTICLE, article)) + errors = inspect(site_repo).errors + assert errors == () if case["visible"] else any("authored" in error for error in errors) From 1a2f09b481d2c39a591f16d38190664c38bfee3d Mon Sep 17 00:00:00 2001 From: hellices Date: Mon, 14 Sep 2026 14:28:04 +0900 Subject: [PATCH 26/26] fix(docs): restore native disclosures and require audit gates Use the browser's default legend for visible, interactable summaryless details while preserving inherited blockers and open-content behavior. Replace the superseded conservative policy with native Chromium oracle coverage. Require unconditional, failure-gating audit steps and validate/build jobs without weakening exact placement or literal-reference checks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/contributing/index.md | 12 +++-- scripts/docs/pre_pages_visibility.py | 2 +- .../pre_pages_default_summary_chromium.json | 14 +++++ .../pre_pages_implied_end_chromium.json | 6 +-- tests/docs/test_pre_pages_content.py | 33 ++++++++++-- tests/docs/test_pre_pages_site.py | 53 ++++++++++++++++--- tests/docs/test_workflows.py | 45 ++++++++++++++++ 7 files changed, 145 insertions(+), 20 deletions(-) create mode 100644 tests/docs/fixtures/pre_pages_default_summary_chromium.json diff --git a/docs/contributing/index.md b/docs/contributing/index.md index 504fd7cf..4dea1061 100644 --- a/docs/contributing/index.md +++ b/docs/contributing/index.md @@ -318,10 +318,12 @@ python scripts/docs/audit_pre_pages.py `tests/docs/**`의 일반 소스 파일이어야 합니다. 추적되지 않은 파일, ignore된 파일, 생성 결과물과 심볼릭 링크는 대체 근거로 사용할 수 없습니다. 빈 `current_paths`는 삭제된 로컬 상태를 제외할 때만 허용합니다. -가시성 감사는 닫힌 `
`의 본문에 대해 작성된 직접 자식 -``를 요구합니다. 브라우저의 기본 컨트롤에만 의존하는 본문은 -보수적으로 제외하지만, 다른 요소 뒤에 오는 첫 `` 요소는 -브라우저 동작에 따라 인정합니다. 이미 `open`인 본문은 별도로 취급합니다. +가시성 감사는 닫힌 `
`를 첫 직접 자식 `` 또는 브라우저의 +기본 컨트롤로 펼칠 수 있는지 검사합니다. 작성된 summary가 없어도 +컨트롤이 보이고 상호작용 가능하면 본문을 인정합니다. 상속된 `inert`, +`hidden`, `display:none`, `aria-hidden` 등의 접근 제한은 계속 반영합니다. +다른 요소 뒤에 오는 첫 ``도 인정하며, 이미 `open`인 본문은 +별도로 가시성을 확인합니다. 전체 감사는 기준선 밖의 신규 문서까지 포함해 현재 canonical 문서 **전체**의 본문 가시성, 검색, 서비스별 주제 진입점, 글 찾기 연결과 로컬 자산을 검사합니다. @@ -339,6 +341,8 @@ Audited 359 baseline files and 73 Markdown files: 62/62 baseline documents mappe 필수입니다. 로컬 clone이 얕으면 검사를 건너뛰지 말고 `git fetch --unshallow` 또는 동등한 전체 이력 fetch를 수행한 뒤 다시 실행합니다. CI에서는 checkout `fetch-depth: 0`으로 같은 조건을 보장합니다. +필수 감사 step과 이를 포함한 validate/build job에는 실행을 건너뛸 수 있는 +`if`를 두지 않으며, `continue-on-error`로 감사 실패를 무시하지 않습니다. 생성된 `site/`과 검색 인덱스는 커밋하지 않습니다. 실패한 검증을 무시하지 않습니다. diff --git a/scripts/docs/pre_pages_visibility.py b/scripts/docs/pre_pages_visibility.py index 56b2e9f9..1c9fe58c 100644 --- a/scripts/docs/pre_pages_visibility.py +++ b/scripts/docs/pre_pages_visibility.py @@ -226,7 +226,7 @@ def _collect( if isinstance(child, _Element) and child.tag == "summary" ), None) if tag == "details" else None closed_body = tag == "details" and "open" not in attributes and not ( - self._summary_hit(summary) if summary else False + self._summary_hit(summary) if summary else state.interactive ) for child in element.children: child_blocked = blocked or closed_body and child is not summary diff --git a/tests/docs/fixtures/pre_pages_default_summary_chromium.json b/tests/docs/fixtures/pre_pages_default_summary_chromium.json new file mode 100644 index 00000000..d57c8df7 --- /dev/null +++ b/tests/docs/fixtures/pre_pages_default_summary_chromium.json @@ -0,0 +1,14 @@ +{ + "engine": "HeadlessChrome/153.0.0.0", + "captured_at": "2026-09-14", + "viewport": [1200, 900], + "method": "Trusted CDP pointer click on the native default details legend; no authored summary exists. ARIA exclusion is an audit accessibility rule, not a pointer-event blocker.", + "cases": [ + {"id": "default", "ancestor": "", "authored_summary": false, "pointer_open": true, "body_visible": true, "audit_open": true}, + {"id": "inert", "ancestor": "inert", "authored_summary": false, "pointer_open": false, "body_visible": false, "audit_open": false}, + {"id": "hidden", "ancestor": "hidden", "authored_summary": false, "pointer_open": false, "body_visible": false, "audit_open": false}, + {"id": "display-none", "ancestor": "style=\"display:none\"", "authored_summary": false, "pointer_open": false, "body_visible": false, "audit_open": false}, + {"id": "aria-hidden", "ancestor": "aria-hidden=\"true\"", "authored_summary": false, "pointer_open": true, "body_visible": true, "audit_open": false}, + {"id": "visibility-hidden", "ancestor": "style=\"visibility:hidden\"", "authored_summary": false, "pointer_open": false, "body_visible": false, "audit_open": false} + ] +} diff --git a/tests/docs/fixtures/pre_pages_implied_end_chromium.json b/tests/docs/fixtures/pre_pages_implied_end_chromium.json index 5637c1ee..2f2db1ea 100644 --- a/tests/docs/fixtures/pre_pages_implied_end_chromium.json +++ b/tests/docs/fixtures/pre_pages_implied_end_chromium.json @@ -2,7 +2,7 @@ "engine": "HeadlessChrome/153.0.0.0", "mode": "CSS1Compat", "captured_at": "2026-09-14", - "method": "Native Chromium DOM parsing and checkVisibility; details activated through the first direct summary element's click(). No-summary audit policy requires an authored control, not an assumed UA fallback.", + "method": "Native Chromium DOM parsing and checkVisibility; authored details controls activated through the first direct summary element's click(). Summaryless default-legend pointer behavior is also recorded.", "cases": [ {"id": "p-p", "html": "

public", "parent": "main", "visible": true}, {"id": "p-div", "html": "

public
", "parent": "main", "visible": true}, @@ -34,7 +34,7 @@ }, "summaryless_policy": { "native_fallback_pointer_open": true, - "audit_open": false, - "reason": "The audit requires an authored direct summary instead of relying on a user-agent fallback." + "audit_open": true, + "reason": "A visible/interactable native default legend is an effective disclosure control." } } diff --git a/tests/docs/test_pre_pages_content.py b/tests/docs/test_pre_pages_content.py index 6b3f88ab..ece1966e 100644 --- a/tests/docs/test_pre_pages_content.py +++ b/tests/docs/test_pre_pages_content.py @@ -1973,11 +1973,10 @@ def test_real_s1_hidden_svg_image_cannot_satisfy_its_preservation_approval(tmp_p @pytest.mark.parametrize("prefix", ["", "
Not a direct summary
"]) -def test_closed_details_without_authored_summary_cannot_supply_source_evidence(prefix): - with pytest.raises(AuditFormatError, match="hidden.*authored"): - extract_markdown_structure( - f'
{prefix}\n\n# Title\n\nAuthored prose.\n\n
' - ) +def test_closed_details_without_authored_summary_use_the_native_control(prefix): + assert extract_markdown_structure( + f'
{prefix}\n\n# Title\n\nAuthored prose.\n\n
' + ).title == "Title" def test_already_open_details_without_summary_keep_visible_source_evidence(): @@ -2024,3 +2023,27 @@ def test_source_raw_container_trailing_markup_cannot_be_promoted(tag): structure = extract_markdown_structure(source) assert structure.images == () assert structure.links == (() if tag == "plaintext" else ("After\nafter.md",)) + + +DEFAULT_SUMMARY_ORACLE = json.loads((ROOT / "tests/docs/fixtures/pre_pages_default_summary_chromium.json").read_text()) + + +@pytest.mark.parametrize("case", DEFAULT_SUMMARY_ORACLE["cases"], ids=lambda case: case["id"]) +def test_native_summaryless_source_visibility_inherits_blockers(case): + source = ( + f'
\n\n' + '# Title\n\n[Visible link](guide.md)\n\n
' + ) + if case["audit_open"]: + structure = extract_markdown_structure(source) + assert structure.title == "Title" and structure.links == ("Visible link\nguide.md",) + else: + with pytest.raises(AuditFormatError, match="hidden.*authored"): + extract_markdown_structure(source) + + +def test_open_summaryless_inert_source_remains_visually_visible(): + structure = extract_markdown_structure( + '
\n\n# Title\n\nVisible prose.\n\n
' + ) + assert structure.title == "Title" diff --git a/tests/docs/test_pre_pages_site.py b/tests/docs/test_pre_pages_site.py index 8ccf2021..d6836955 100644 --- a/tests/docs/test_pre_pages_site.py +++ b/tests/docs/test_pre_pages_site.py @@ -1790,19 +1790,19 @@ def test_html_entity_decoding_for_scheme_checks_happens_only_once(site_repo): @pytest.mark.parametrize("opened", [False, True]) -def test_built_details_without_authored_summary_only_count_when_open(site_repo, opened): +def test_built_summaryless_details_are_accessible_closed_or_open(site_repo, opened): root, _ = site_repo article = ARTICLE.replace("

", f'

').replace( "", "

", ) write(root, "site/" + ENTRY.replace(".md", ".html"), html().replace(ARTICLE, article)) errors = inspect(site_repo).errors - assert errors == () if opened else any("authored" in error for error in errors) + assert errors == () @pytest.mark.parametrize("channel", ["service", "explore", "redirect"]) @pytest.mark.parametrize("late_summary", [False, True]) -def test_generated_page_disclosures_require_a_summary_but_allow_a_late_one(site_repo, channel, late_summary): +def test_generated_page_disclosures_allow_default_or_late_summary(site_repo, channel, late_summary): root, _ = site_repo summary = "

before

Late" if late_summary else "" if channel == "service": @@ -1819,10 +1819,7 @@ def test_generated_page_disclosures_require_a_summary_but_allow_a_late_one(site_ content = redirect(target).replace(anchor, f"
{summary}{anchor}
") write(root, "site/" + path, content) errors = inspect(site_repo).errors - if late_summary: - assert errors == () - else: - assert any("not linked" in error or "fallback" in error for error in errors) + assert errors == () def test_summaryless_details_still_validate_fetched_assets(site_repo): @@ -1927,3 +1924,45 @@ def test_built_visibility_inherit_unset_and_restore_controls(site_repo, case): write(root, "site/" + ENTRY.replace(".md", ".html"), html().replace(ARTICLE, article)) errors = inspect(site_repo).errors assert errors == () if case["visible"] else any("authored" in error for error in errors) + + +DEFAULT_SUMMARY_ORACLE = json.loads((ROOT / "tests/docs/fixtures/pre_pages_default_summary_chromium.json").read_text()) + + +@pytest.mark.parametrize("case", DEFAULT_SUMMARY_ORACLE["cases"], ids=lambda case: case["id"]) +def test_native_summaryless_built_article_inherits_blockers(site_repo, case): + root, _ = site_repo + article = ARTICLE.replace("

", f'

').replace( + "", "

", + ) + write(root, "site/" + ENTRY.replace(".md", ".html"), html().replace(ARTICLE, article)) + errors = inspect(site_repo).errors + assert errors == () if case["audit_open"] else any("authored" in error for error in errors) + + +@pytest.mark.parametrize("case", DEFAULT_SUMMARY_ORACLE["cases"], ids=lambda case: case["id"]) +@pytest.mark.parametrize("channel", ["service", "explore", "redirect"]) +def test_native_summaryless_reachability_inherits_blockers(site_repo, case, channel): + root, _ = site_repo + + def wrapped(anchor): + return f'
{anchor}
' + + if channel == "service": + path = "services/service/index.html" + content = html(wrapped('Topic')) + elif channel == "explore": + path = "explore/index.html" + content = html(wrapped('Topic') + + 'Child') + else: + path = OLD.replace(".md", ".html") + target = "../../../services/service/topic/" + anchor = f'Moved document' + content = redirect(target).replace(anchor, wrapped(anchor)) + write(root, "site/" + path, content) + errors = inspect(site_repo).errors + if case["audit_open"]: + assert errors == () + else: + assert any("not linked" in error or "fallback" in error for error in errors) diff --git a/tests/docs/test_workflows.py b/tests/docs/test_workflows.py index 5a298709..94867882 100644 --- a/tests/docs/test_workflows.py +++ b/tests/docs/test_workflows.py @@ -119,6 +119,9 @@ def assert_pre_pages_audit_runs_after_search_validation( for workflow_name, job_name in (("docs-ci.yml", "validate"), ("pages.yml", "build")): jobs = jobs_by_workflow[workflow_name] assert job_name in jobs, workflow_name + job_definition = yaml.safe_load((workflows_dir / workflow_name).read_text())["jobs"][job_name] + assert "if" not in job_definition, (workflow_name, job_name, "conditional audit job") + assert job_definition.get("continue-on-error", False) is False, (workflow_name, job_name, "audit job must fail") steps = jobs[job_name] search_positions = [ index @@ -131,6 +134,8 @@ def assert_pre_pages_audit_runs_after_search_validation( assert audit_position < len(steps), workflow_name audit_step = steps[audit_position] assert audit_step.get("name") == "Audit pre-Pages content preservation" + assert "if" not in audit_step, (workflow_name, "conditional audit step") + assert audit_step.get("continue-on-error", False) is False, (workflow_name, "audit step must fail") audit_run = audit_step.get("run") assert isinstance(audit_run, str), workflow_name assert audit_run.strip() == AUDIT_COMMAND, workflow_name @@ -141,6 +146,46 @@ def assert_pre_pages_audit_runs_after_search_validation( ) == 0 +def mutate_audit_gate(tmp_path, workflow_name, job_name, scope, field, value): + for path in WORKFLOWS.glob("*.yml"): + write_workflow(tmp_path / path.name, path.read_text()) + path = tmp_path / workflow_name + data = yaml.safe_load(path.read_text()) + job = data["jobs"][job_name] + target = job if scope == "job" else next( + step for step in job["steps"] if step.get("name") == "Audit pre-Pages content preservation" + ) + target[field] = value + write_workflow(path, yaml.safe_dump(data)) + + +@pytest.mark.parametrize(("workflow", "job"), [("docs-ci.yml", "validate"), ("pages.yml", "build")]) +@pytest.mark.parametrize("scope", ["step", "job"]) +@pytest.mark.parametrize("condition", [ + False, True, None, "${{ false }}", "${{ github.ref == 'refs/heads/main' }}", "always()", +]) +def test_mandatory_audit_gate_rejects_every_if_key(tmp_path, workflow, job, scope, condition): + mutate_audit_gate(tmp_path, workflow, job, scope, "if", condition) + with pytest.raises(AssertionError): + assert_pre_pages_audit_runs_after_search_validation(tmp_path) + + +@pytest.mark.parametrize(("workflow", "job"), [("docs-ci.yml", "validate"), ("pages.yml", "build")]) +@pytest.mark.parametrize("scope", ["step", "job"]) +@pytest.mark.parametrize("continuation", [True, "true", "${{ true }}", "${{ github.event_name == 'push' }}", None]) +def test_mandatory_audit_gate_rejects_failure_suppression(tmp_path, workflow, job, scope, continuation): + mutate_audit_gate(tmp_path, workflow, job, scope, "continue-on-error", continuation) + with pytest.raises(AssertionError): + assert_pre_pages_audit_runs_after_search_validation(tmp_path) + + +@pytest.mark.parametrize(("workflow", "job"), [("docs-ci.yml", "validate"), ("pages.yml", "build")]) +@pytest.mark.parametrize("scope", ["step", "job"]) +def test_mandatory_audit_gate_allows_explicit_false_continuation(tmp_path, workflow, job, scope): + mutate_audit_gate(tmp_path, workflow, job, scope, "continue-on-error", False) + assert_pre_pages_audit_runs_after_search_validation(tmp_path) + + def test_quoted_uses_values_are_detected_and_rejected(tmp_path: Path) -> None: workflow = tmp_path / "quoted.yml" write_workflow(