From 3cf47f3fdb94da5243805fb57ace6f871f700aa4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 29 Sep 2026 07:58:14 +0900 Subject: [PATCH 01/11] test(strix): measure report-scope gate in process and document validate The report-scope helper from #2474 was exercised only through subprocesses, so the repository coverage gate measured it at 0%, and validate() had no docstring for the interrogate gate. Add in-process cases for every fail-closed branch and both CLI outcomes; the subprocess contract stays. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HMFn3QpKVj9ptCjtYDBp55 --- scripts/ci/strix_report_scope.py | 1 + tests/test_strix_report_scope.py | 85 ++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/scripts/ci/strix_report_scope.py b/scripts/ci/strix_report_scope.py index 44de1c441d..a2ca12cc97 100644 --- a/scripts/ci/strix_report_scope.py +++ b/scripts/ci/strix_report_scope.py @@ -9,6 +9,7 @@ def validate(output: Path, changed_paths: list[str]) -> None: + """Raise ValueError unless one completed, unlinked report names a changed path.""" if not output.is_dir() or output.is_symlink(): raise ValueError("scan output directory is missing") runs = [path for path in output.iterdir() if path.is_dir() and not path.is_symlink()] diff --git a/tests/test_strix_report_scope.py b/tests/test_strix_report_scope.py index 3ff7964659..469ca8705c 100644 --- a/tests/test_strix_report_scope.py +++ b/tests/test_strix_report_scope.py @@ -1,10 +1,14 @@ """A completed Strix report must name the PR source it assessed.""" import json +import runpy import subprocess import sys from pathlib import Path +import pytest + +from scripts.ci import strix_report_scope as scope SCRIPT = Path(__file__).resolve().parents[1] / "scripts/ci/strix_report_scope.py" @@ -24,3 +28,84 @@ def test_report_scope_rejects_unrelated_success_and_accepts_scoped_success(tmp_p report.write_text("Assessed python/fast_mlsirm/report.py; no vulnerabilities found.\n", encoding="utf-8") assert subprocess.run(command, capture_output=True).returncode == 0 assert subprocess.run(command[:-1], capture_output=True).returncode == 1 + + +CHANGED = "python/fast_mlsirm/report.py" + + +def _scan(tmp_path: Path, metadata: object, report: str = f"Assessed {CHANGED}.\n") -> Path: + run = tmp_path / "run" + run.mkdir() + (run / "run.json").write_text(json.dumps(metadata), encoding="utf-8") + (run / "penetration_test_report.md").write_text(report, encoding="utf-8") + return run + + +COMPLETED = {"status": "completed", "scan_results": {"scan_completed": True, "success": True}} + + +def test_validate_accepts_one_completed_report_naming_changed_source(tmp_path: Path) -> None: + _scan(tmp_path, COMPLETED) + scope.validate(tmp_path, [CHANGED]) + + +@pytest.mark.parametrize( + ("metadata", "report", "message"), + [ + ([], "", "scan metadata is not an object"), + ({"status": "completed", "scan_results": ["completed"]}, "", "scan results are not an object"), + ({"status": "running"}, "", "scan report is incomplete"), + ({"status": "completed", "scan_results": {"scan_completed": True, "success": False}}, "", "scan report is incomplete"), + (COMPLETED, "No source named.\n", "does not identify a changed source file"), + ], +) +def test_validate_rejects_untrusted_report_content(tmp_path: Path, metadata: object, report: str, message: str) -> None: + _scan(tmp_path, metadata, report) + with pytest.raises(ValueError, match=message): + scope.validate(tmp_path, [CHANGED]) + + +def test_validate_rejects_missing_or_linked_output_directory(tmp_path: Path) -> None: + with pytest.raises(ValueError, match="output directory is missing"): + scope.validate(tmp_path / "absent", [CHANGED]) + real = tmp_path / "real" + real.mkdir() + (tmp_path / "linked").symlink_to(real, target_is_directory=True) + with pytest.raises(ValueError, match="output directory is missing"): + scope.validate(tmp_path / "linked", [CHANGED]) + + +def test_validate_requires_exactly_one_unlinked_run(tmp_path: Path) -> None: + with pytest.raises(ValueError, match="exactly one current scan report"): + scope.validate(tmp_path, [CHANGED]) + _scan(tmp_path, COMPLETED) + (tmp_path / "second").mkdir() + with pytest.raises(ValueError, match="exactly one current scan report"): + scope.validate(tmp_path, [CHANGED]) + + +def test_validate_rejects_missing_or_linked_report_files(tmp_path: Path) -> None: + run = _scan(tmp_path, COMPLETED) + report = run / "penetration_test_report.md" + report.unlink() + with pytest.raises(ValueError, match="missing or linked"): + scope.validate(tmp_path, [CHANGED]) + elsewhere = tmp_path.parent / f"{tmp_path.name}-report.md" + elsewhere.write_text(f"Assessed {CHANGED}.\n", encoding="utf-8") + report.symlink_to(elsewhere) + with pytest.raises(ValueError, match="missing or linked"): + scope.validate(tmp_path, [CHANGED]) + + +def test_cli_exits_zero_on_scoped_report_and_one_with_bounded_error( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] +) -> None: + _scan(tmp_path, COMPLETED) + monkeypatch.setattr(sys, "argv", [str(SCRIPT), str(tmp_path), CHANGED]) + runpy.run_path(str(SCRIPT), run_name="__main__") + + monkeypatch.setattr(sys, "argv", [str(SCRIPT)]) + with pytest.raises(SystemExit) as exit_info: + runpy.run_path(str(SCRIPT), run_name="__main__") + assert exit_info.value.code == 1 + assert "ERROR: Strix report scope:" in capsys.readouterr().err From e58f606cb1d3d789825af7e8d7a4a4102f94edff Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 29 Sep 2026 13:15:45 +0900 Subject: [PATCH 02/11] test(strix): require directory-scoped file names to identify changed source #2291's hosted Strix report named its scope as scripts/ci/ and the changed file as strix_quick_gate.sh, yet the report-scope gate demanded the literal repository path and failed closed. Pin that shape as accepted while bare, prefixed, suffixed, and wrong-directory names stay rejected. Co-Authored-By: Claude Opus 5.5 --- tests/test_strix_report_scope.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_strix_report_scope.py b/tests/test_strix_report_scope.py index 469ca8705c..79732a4038 100644 --- a/tests/test_strix_report_scope.py +++ b/tests/test_strix_report_scope.py @@ -109,3 +109,29 @@ def test_cli_exits_zero_on_scoped_report_and_one_with_bounded_error( runpy.run_path(str(SCRIPT), run_name="__main__") assert exit_info.value.code == 1 assert "ERROR: Strix report scope:" in capsys.readouterr().err + + +def test_validate_accepts_changed_file_named_within_its_reported_directory(tmp_path: Path) -> None: + """Reports often name the scanned directory once and each file by name (#2291).""" + _scan( + tmp_path, + COMPLETED, + "Scope: `/workspace/strix-pr-scope.v6Wilu/scripts/ci/`.\n" + "A security review of `strix_quick_gate.sh` and `strix_model_utils.sh`.\n", + ) + scope.validate(tmp_path, ["scripts/ci/strix_quick_gate.sh"]) + + +@pytest.mark.parametrize( + "report", + [ + "Reviewed `strix_quick_gate.sh` without naming its directory.\n", + "Scope: scripts/ci/. Reviewed `my_strix_quick_gate.sh`.\n", + "Scope: scripts/ci/. Reviewed `strix_quick_gate.sh.bak`.\n", + "Scope: other/scripts/ci-tools/. Reviewed strix_quick_gate.sh.\n", + ], +) +def test_validate_rejects_bare_or_partial_file_names(tmp_path: Path, report: str) -> None: + _scan(tmp_path, COMPLETED, report) + with pytest.raises(ValueError, match="does not identify a changed source file"): + scope.validate(tmp_path, ["scripts/ci/strix_quick_gate.sh"]) From 2747d2e89f0ceae41eecef0f7d1657cc6c7489ef Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 29 Sep 2026 13:16:26 +0900 Subject: [PATCH 03/11] fix(strix): accept changed files named within their reported directory A report that names the scanned directory and each changed file by name now identifies that changed source. The file name must be a standalone token, so prefixed or suffixed names do not match, and a bare name without its directory is still rejected. The #2238 unscoped-report guard is unchanged. Replaying #2291's hosted report: old gate rejects, new accepts. Co-Authored-By: Claude Opus 5.5 --- scripts/ci/strix_report_scope.py | 12 +++++++++++- tests/test_strix_report_scope.py | 5 +++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/scripts/ci/strix_report_scope.py b/scripts/ci/strix_report_scope.py index a2ca12cc97..943d2b01f6 100644 --- a/scripts/ci/strix_report_scope.py +++ b/scripts/ci/strix_report_scope.py @@ -4,10 +4,20 @@ from __future__ import annotations import json +import re import sys from pathlib import Path +def names_changed_path(report: str, path: str) -> bool: + """Return whether the report names the path, or its file within its directory.""" + if path in report: + return True + directory, _, name = path.rpartition("/") + file_token = re.compile(rf"(? None: """Raise ValueError unless one completed, unlinked report names a changed path.""" if not output.is_dir() or output.is_symlink(): @@ -29,7 +39,7 @@ def validate(output: Path, changed_paths: list[str]) -> None: if metadata.get("status") != "completed" or results.get("scan_completed") is not True or results.get("success") is not True: raise ValueError("scan report is incomplete") report = report_path.read_text(encoding="utf-8") - if not any(path in report for path in changed_paths): + if not any(names_changed_path(report, path) for path in changed_paths): raise ValueError("scan report does not identify a changed source file") diff --git a/tests/test_strix_report_scope.py b/tests/test_strix_report_scope.py index 79732a4038..7a70a0f76d 100644 --- a/tests/test_strix_report_scope.py +++ b/tests/test_strix_report_scope.py @@ -122,6 +122,11 @@ def test_validate_accepts_changed_file_named_within_its_reported_directory(tmp_p scope.validate(tmp_path, ["scripts/ci/strix_quick_gate.sh"]) +def test_validate_accepts_file_name_ending_a_sentence(tmp_path: Path) -> None: + _scan(tmp_path, COMPLETED, "Scope: scripts/ci/. The review covered strix_quick_gate.sh.\n") + scope.validate(tmp_path, ["scripts/ci/strix_quick_gate.sh"]) + + @pytest.mark.parametrize( "report", [ From 6f7dcb373c286c9bd6e9ce2978635824e2d12f12 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 29 Sep 2026 16:45:36 +0900 Subject: [PATCH 04/11] test(strix): accept file names scoped by a reported ancestor directory fast-mlsirm#2052's correctly scoped report named crates/mlsirm-core and two_tier_recursion.rs but not crates/mlsirm-core/src/, and failed closed. Single-segment ancestors and prefix-extended directories stay rejected. Co-Authored-By: Claude Opus 5.5 --- tests/test_strix_report_scope.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_strix_report_scope.py b/tests/test_strix_report_scope.py index 7a70a0f76d..1fef58e1f1 100644 --- a/tests/test_strix_report_scope.py +++ b/tests/test_strix_report_scope.py @@ -140,3 +140,29 @@ def test_validate_rejects_bare_or_partial_file_names(tmp_path: Path, report: str _scan(tmp_path, COMPLETED, report) with pytest.raises(ValueError, match="does not identify a changed source file"): scope.validate(tmp_path, ["scripts/ci/strix_quick_gate.sh"]) + + +def test_validate_accepts_file_name_within_a_reported_ancestor_directory(tmp_path: Path) -> None: + """fast-mlsirm#2052 named the crate directory and the file, not ``src/``.""" + _scan( + tmp_path, + COMPLETED, + "**Scope:** `/workspace/strix-pr-scope.4eyzTL` (including\n" + "`crates/mlsirm-core` and `python/fast_mlsirm`).\n" + "- **Rust Audit:** Review of the `two_tier_recursion.rs` and `lib.rs`.\n", + ) + scope.validate(tmp_path, ["crates/mlsirm-core/src/two_tier_recursion.rs"]) + + +@pytest.mark.parametrize( + "report", + [ + "Scope: crates/ only. Reviewed two_tier_recursion.rs.\n", + "Scope: crates/mlsirm-core-extra. Reviewed two_tier_recursion.rs.\n", + "Scope: crates/mlsirm-core. Reviewed other_recursion.rs.\n", + ], +) +def test_validate_rejects_generic_or_mismatched_ancestors(tmp_path: Path, report: str) -> None: + _scan(tmp_path, COMPLETED, report) + with pytest.raises(ValueError, match="does not identify a changed source file"): + scope.validate(tmp_path, ["crates/mlsirm-core/src/two_tier_recursion.rs"]) From 9df9402531e3e846e1684a4a94ebce7a01505812 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 29 Sep 2026 16:45:50 +0900 Subject: [PATCH 05/11] fix(strix): accept file names scoped by a reported ancestor directory A changed file is identified when its name is a standalone token and the report names its directory or any ancestor of at least two segments. Replayed hosted reports: fast-mlsirm#2052 and .github#2291 are accepted; fast-mlsirm#2083's hallucinated /api/users report is still rejected. Co-Authored-By: Claude Opus 5.5 --- scripts/ci/strix_report_scope.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/scripts/ci/strix_report_scope.py b/scripts/ci/strix_report_scope.py index 943d2b01f6..e1961c29d5 100644 --- a/scripts/ci/strix_report_scope.py +++ b/scripts/ci/strix_report_scope.py @@ -9,13 +9,26 @@ from pathlib import Path +def _token(text: str) -> re.Pattern[str]: + """Match text only where it is not part of a longer name or path segment.""" + return re.compile(rf"(? bool: - """Return whether the report names the path, or its file within its directory.""" + """Return whether the report names the path, or its file within a named directory. + + The directory may be the file's own directory or any ancestor of at least two + segments; a lone top-level name such as ``crates`` is too generic to scope a file. + """ if path in report: return True directory, _, name = path.rpartition("/") - file_token = re.compile(rf"(? None: From 63e3aebd8b66fd79b85ab6b1aa95e53f55f1ae46 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 29 Sep 2026 23:13:38 +0900 Subject: [PATCH 06/11] test(strix): reject filename path-segment suffix --- tests/test_strix_report_scope.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_strix_report_scope.py b/tests/test_strix_report_scope.py index 1fef58e1f1..eccf50abc4 100644 --- a/tests/test_strix_report_scope.py +++ b/tests/test_strix_report_scope.py @@ -159,6 +159,7 @@ def test_validate_accepts_file_name_within_a_reported_ancestor_directory(tmp_pat [ "Scope: crates/ only. Reviewed two_tier_recursion.rs.\n", "Scope: crates/mlsirm-core-extra. Reviewed two_tier_recursion.rs.\n", + "Scope: crates/mlsirm-core. Reviewed two_tier_recursion.rs/notes.\n", "Scope: crates/mlsirm-core. Reviewed other_recursion.rs.\n", ], ) From 6295bf81938387875d583a39c9d812fc17aa4725 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 30 Sep 2026 01:26:55 +0900 Subject: [PATCH 07/11] fix(strix): reject child paths after reported file names --- scripts/ci/strix_report_scope.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/ci/strix_report_scope.py b/scripts/ci/strix_report_scope.py index e1961c29d5..2b4f62faf1 100644 --- a/scripts/ci/strix_report_scope.py +++ b/scripts/ci/strix_report_scope.py @@ -14,6 +14,11 @@ def _token(text: str) -> re.Pattern[str]: return re.compile(rf"(? re.Pattern[str]: + """Match a file name only when no longer name or child path continues it.""" + return re.compile(rf"(? bool: """Return whether the report names the path, or its file within a named directory. @@ -23,7 +28,7 @@ def names_changed_path(report: str, path: str) -> bool: if path in report: return True directory, _, name = path.rpartition("/") - if not directory or _token(name).search(report) is None: + if not directory or _file_token(name).search(report) is None: return False if f"{directory}/" in report: return True From b0cb823eb15624c9b84b919171a1a28839733fda Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 30 Sep 2026 10:04:31 +0900 Subject: [PATCH 08/11] test(strix): give report-scope fixtures complete finish fields #2492 makes strix_report_scope.py require the four persisted Strix finish fields (executive_summary, methodology, technical_analysis, recommendations) to be present and non-placeholder. Give this PR's completed-scan fixtures real values now, so the tests stay valid whichever of the two PRs lands first. The current validator ignores the extra fields. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HMFn3QpKVj9ptCjtYDBp55 --- tests/test_strix_report_scope.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_strix_report_scope.py b/tests/test_strix_report_scope.py index b0b5fb78fc..69c05e2d84 100644 --- a/tests/test_strix_report_scope.py +++ b/tests/test_strix_report_scope.py @@ -17,7 +17,7 @@ def test_report_scope_rejects_unrelated_success_and_accepts_scoped_success(tmp_p run = tmp_path / "current-scan" run.mkdir() (run / "run.json").write_text( - json.dumps({"status": "completed", "scan_results": {"scan_completed": True, "success": True}}), + json.dumps({"status": "completed", "scan_results": {"scan_completed": True, "success": True, "executive_summary": "No issues in the changed file.", "methodology": "Reviewed the changed source file.", "technical_analysis": "No untrusted input reaches the change.", "recommendations": "No remediation required."}}), encoding="utf-8", ) report = run / "penetration_test_report.md" @@ -41,7 +41,7 @@ def _scan(tmp_path: Path, metadata: object, report: str = f"Assessed {CHANGED}.\ return run -COMPLETED = {"status": "completed", "scan_results": {"scan_completed": True, "success": True}} +COMPLETED = {"status": "completed", "scan_results": {"scan_completed": True, "success": True, "executive_summary": "No issues in the changed file.", "methodology": "Reviewed the changed source file.", "technical_analysis": "No untrusted input reaches the change.", "recommendations": "No remediation required."}} def test_validate_accepts_one_completed_report_naming_changed_source(tmp_path: Path) -> None: @@ -186,7 +186,7 @@ def _completed_run(tmp_path: Path, report: str) -> None: run = tmp_path / "current-scan" run.mkdir() (run / "run.json").write_text( - json.dumps({"status": "completed", "scan_results": {"scan_completed": True, "success": True}}), + json.dumps({"status": "completed", "scan_results": {"scan_completed": True, "success": True, "executive_summary": "No issues in the changed file.", "methodology": "Reviewed the changed source file.", "technical_analysis": "No untrusted input reaches the change.", "recommendations": "No remediation required."}}), encoding="utf-8", ) (run / "penetration_test_report.md").write_text(report, encoding="utf-8") From fcf03118df421be7eff73964e711aaf1cd8312e6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 30 Sep 2026 13:58:53 +0900 Subject: [PATCH 09/11] test(strix): reject longer and unrelated report paths RED: current names_changed_path accepts a full-path backup/child suffix and a same-named file under an unrelated nested directory. --- tests/test_strix_report_scope.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_strix_report_scope.py b/tests/test_strix_report_scope.py index 69c05e2d84..ccfa41abe0 100644 --- a/tests/test_strix_report_scope.py +++ b/tests/test_strix_report_scope.py @@ -142,6 +142,19 @@ def test_validate_rejects_bare_or_partial_file_names(tmp_path: Path, report: str scope.validate(tmp_path, ["scripts/ci/strix_quick_gate.sh"]) +@pytest.mark.parametrize( + "report", + [ + "Reviewed scripts/ci/strix_quick_gate.sh.bak.\\n", + "Reviewed scripts/ci/strix_quick_gate.sh/notes.\\n", + "Scope: other/scripts/ci/. Reviewed other/strix_quick_gate.sh.\\n", + ], +) +def test_names_changed_path_rejects_longer_or_unrelated_paths(report: str) -> None: + """A suffix or same-named file under another directory is not the changed file.""" + assert not scope.names_changed_path(report, "scripts/ci/strix_quick_gate.sh") + + def test_validate_accepts_file_name_within_a_reported_ancestor_directory(tmp_path: Path) -> None: """fast-mlsirm#2052 named the crate directory and the file, not ``src/``.""" _scan( From ff64cfaa607a413976a8a85c9cd5a003289ef292 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 30 Sep 2026 13:59:27 +0900 Subject: [PATCH 10/11] fix(strix): bind report names to complete path tokens Reject backup/child suffixes and same-named files nested under an unrelated directory while preserving standalone relative paths and canonical PR-scope ancestry. --- scripts/ci/strix_report_scope.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/ci/strix_report_scope.py b/scripts/ci/strix_report_scope.py index aabe3dd81c..1f88096f54 100644 --- a/scripts/ci/strix_report_scope.py +++ b/scripts/ci/strix_report_scope.py @@ -24,12 +24,12 @@ def _names_scoped_ancestor(report: str, changed_paths: list[str]) -> bool: def _token(text: str) -> re.Pattern[str]: """Match text only where it is not part of a longer name or path segment.""" - return re.compile(rf"(? re.Pattern[str]: """Match a file name only when no longer name or child path continues it.""" - return re.compile(rf"(? bool: @@ -38,12 +38,12 @@ def names_changed_path(report: str, path: str) -> bool: The directory may be the file's own directory or any ancestor of at least two segments; a lone top-level name such as ``crates`` is too generic to scope a file. """ - if path in report: + if _file_token(path).search(report) is not None: return True directory, _, name = path.rpartition("/") if not directory or _file_token(name).search(report) is None: return False - if f"{directory}/" in report: + if _token(directory).search(report) is not None: return True parts = directory.split("/") return any(_token("/".join(parts[:depth])).search(report) for depth in range(2, len(parts) + 1)) From 572e75d6c5e2d0fe694417c657fc01821afd23dd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 30 Sep 2026 14:02:20 +0900 Subject: [PATCH 11/11] docs(strix): record path-token false-positive repair Keep the Gap Proposed until fresh exact-head hosted checks and independent review settle. --- CHANGELOG.d/20260930-strix-report-path-token-boundary.md | 5 +++++ docs/product-technical-gap-baseline.md | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 CHANGELOG.d/20260930-strix-report-path-token-boundary.md diff --git a/CHANGELOG.d/20260930-strix-report-path-token-boundary.md b/CHANGELOG.d/20260930-strix-report-path-token-boundary.md new file mode 100644 index 0000000000..37285c6e3a --- /dev/null +++ b/CHANGELOG.d/20260930-strix-report-path-token-boundary.md @@ -0,0 +1,5 @@ +## Fixed + +- Bind Strix changed-source report evidence to complete path tokens so backup or + child suffixes and same-named files under unrelated directories cannot satisfy + the central review-scope gate. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 6e5f1c549a..129397ca70 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -7,6 +7,12 @@ 이 문서는 제품·기술·운영 Gap을 현재 문서와 현재 GitHub 상태에 묶어 두는 기준선이다. 새 작업은 먼저 이 문서의 Gap ID를 PR 설명과 테스트 증거에 연결하고, PR의 정확한 exact HEAD·Checks·리뷰를 다시 수집한 뒤 구현한다. 표의 상태는 작성 시점의 관측값이므로, 병합 판단에는 재사용하지 않는다. 이 인벤토리는 스냅샷이며 merge authorization이 아니다. +### 2026-09-30 Strix report path-token false-positive delta + +| Gap ID | 상태 | exact evidence | causal owner / next gate | +|---|---|---|---| +| CONTROL-STRIX-REPORT-PATH-TOKEN-02 | **Proposed — RED→GREEN source repair; hosted exact-head acceptance pending** | `.github#2504`의 이전 `names_changed_path`는 전체 경로의 `.bak`/child suffix와 `other/scripts/ci/` 아래 동명 파일을 changed source로 오인했다. RED `fcf03118df421be7eff73964e711aaf1cd8312e6`이 두 오탐을 executable regression으로 고정했고, GREEN `ff64cfaa607a413976a8a85c9cd5a003289ef292`은 direct path·directory·filename을 완전 token 경계로 검증한다. focused path-boundary 계약은 7/7 GREEN이며 protected `main@37b10243cec3d160ecc9c1be75c71428b160a703`를 ordinary merge `e8fd6123c1ff6f4fd89848d15f07cb6ae5eb35b4`로 통합했다. | Canonical owner는 중앙 `scripts/ci/strix_report_scope.py`다. exact-head hosted security/quality Checks와 fresh independent review가 terminal GREEN이 되기 전에는 Accepted·merge authority로 승격하지 않는다. | + ### 2026-09-19 exact-head incident delta | Gap ID | 상태 | exact-head evidence | causal owner / next gate |