Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions src/operator_os_seam_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
from src.portfolio_truth_render import GENERATED_MARKDOWN_PROVENANCE_MARKER
from src.portfolio_truth_types import LEGACY_SCHEMA_VERSIONS, SCHEMA_VERSION, truth_latest_path
from src.portfolio_catalog import load_portfolio_catalog
from src.portfolio_truth_sources import (
WORKSPACE_DISCOVERY_POLICY_VERSION,
workspace_exclusion_reason,
)
from src.project_registry import (
BRIDGE_CANONICAL_KEY_DISAGREEMENTS,
DEFAULT_NOTION_PROJECTION_ONLY_ROWS,
Expand Down Expand Up @@ -427,6 +431,7 @@ def _check_contract_shadow(
)

findings.extend(_check_rollup_integrity(truth, truth_path=truth_path))
findings.extend(_check_exclusion_integrity(truth, truth_path=truth_path))
findings.extend(_check_carried_freshness(truth, truth_path=truth_path, now=now))
return findings

Expand Down Expand Up @@ -560,6 +565,59 @@ def _check_carried_freshness(
return []


def _check_exclusion_integrity(
truth: dict[str, Any], *, truth_path: Path
) -> list[SeamLintFinding]:
exclusions = truth.get("exclusions")
if not isinstance(exclusions, dict):
return [

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scan project paths even when exclusions are missing

When contract-shadow checks an artifact that predates or accidentally drops the new exclusions envelope, this early return prevents the later projects[].identity.path scan from running. In that case a truth file can still contain a path like Documents/Codex Backups/..., but CL-EXCL-001 is reported only as an unknown envelope issue so passed remains true instead of rejecting the leaked backup project; scan for leaked excluded path parts before returning for the missing envelope.

Useful? React with 👍 / 👎.

SeamLintFinding(
check="CL-EXCL-001",
artifact=str(truth_path),
violation="workspace exclusion envelope is absent",
detail="Exclusion policy and counts cannot be verified.",
level="unknown",
)
]
policy_version = exclusions.get("policy_version")
counts = exclusions.get("counts")
if policy_version != WORKSPACE_DISCOVERY_POLICY_VERSION or not isinstance(
counts, dict
):
return [
SeamLintFinding(
check="CL-EXCL-001",
artifact=str(truth_path),
violation="workspace exclusion envelope is incompatible",
detail=(
f"policy_version={policy_version!r}; "
f"expected={WORKSPACE_DISCOVERY_POLICY_VERSION!r}"
),
)
]

leaked: list[str] = []
for project in truth.get("projects", []):
if not isinstance(project, dict):
continue
identity = project.get("identity")
path = identity.get("path") if isinstance(identity, dict) else None
if not isinstance(path, str):
continue
if any(workspace_exclusion_reason(part) for part in Path(path).parts):
leaked.append(path)
if not leaked:
return []
return [
SeamLintFinding(
check="CL-EXCL-001",
artifact=str(truth_path),
violation="excluded workspace paths leaked into portfolio projects",
detail=f"count={len(leaked)}; sample={', '.join(sorted(leaked)[:10])}",
)
]


def _check_schema_pin(
truth: dict[str, Any],
*,
Expand Down
7 changes: 7 additions & 0 deletions src/portfolio_truth_reconcile.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from src.portfolio_pathing import build_operating_path_entry
from src.portfolio_risk import build_risk_entry
from src.portfolio_truth_sources import (
WORKSPACE_DISCOVERY_POLICY_VERSION,
discover_workspace_projects,
load_legacy_registry_rows,
load_safe_notion_project_context,
Expand Down Expand Up @@ -242,10 +243,12 @@ def build_portfolio_truth_snapshot(
len(notion_context),
)

exclusion_counts: dict[str, int] = {}
workspace_projects = discover_workspace_projects(
workspace_root,
catalog_data=catalog_data,
now=now,
exclusion_counts=exclusion_counts,
)
projects = [
_build_truth_project(
Expand Down Expand Up @@ -316,6 +319,10 @@ def build_portfolio_truth_snapshot(
notion_context_carried_forward=notion_context_carried_forward,
prior_notion_generated_at=prior_notion_generated_at,
),
exclusions={
"policy_version": WORKSPACE_DISCOVERY_POLICY_VERSION,
"counts": dict(sorted(exclusion_counts.items())),
},
)
return PortfolioTruthBuildResult(
snapshot=snapshot, catalog_data=catalog_data, legacy_rows=legacy_rows
Expand Down
50 changes: 42 additions & 8 deletions src/portfolio_truth_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,33 +86,54 @@
# nogoprjs -> operator-flagged "no-go" projects, never pursued
# smoke-export -> generated AuraForge signed-smoke-export bundles (no real repo)
IGNORE_PROJECT_DIR_TOKENS = frozenset({"nogoprjs", "smoke-export"})
IGNORE_PROJECT_DIR_NAMES = frozenset({"codex backups"})
# Transient / generated working directories matched by regex on the dir name —
# e.g. a `<repo>-tmp-<timestamp>` clone left behind by a tooling run.
IGNORE_PROJECT_DIR_PATTERNS: tuple[re.Pattern[str], ...] = (re.compile(r"-tmp-\d+$"),)
ARCHIVE_REMOTE_BASENAME_TOKENS = frozenset({"private-archive", "scrubbed-import"})


def _is_ignored_project_dir(name: str) -> bool:
"""True if a directory name is a transient/non-project artifact to skip."""
WORKSPACE_DISCOVERY_POLICY_VERSION = "workspace_discovery.v1"


def workspace_exclusion_reason(name: str) -> str | None:
"""Return the stable policy reason for a non-project directory name."""
lowered = name.lower()
if lowered in IGNORE_PROJECT_DIR_NAMES:
return "backup-container"
if any(token in lowered for token in IGNORE_PROJECT_DIR_TOKENS):
return True
return any(pattern.search(name) for pattern in IGNORE_PROJECT_DIR_PATTERNS)
return "operator-excluded" if "nogoprjs" in lowered else "generated-evidence"
if any(pattern.search(name) for pattern in IGNORE_PROJECT_DIR_PATTERNS):
return "temporary-checkout"
return None


def _is_ignored_project_dir(name: str) -> bool:
"""True if a directory name is a transient/non-project artifact to skip."""
return workspace_exclusion_reason(name) is not None


def _record_exclusion(counts: dict[str, int] | None, reason: str | None) -> None:
if counts is not None and reason is not None:
counts[reason] = counts.get(reason, 0) + 1


def discover_workspace_projects(
workspace_root: Path,
*,
catalog_data: dict[str, Any],
now: datetime | None = None,
exclusion_counts: dict[str, int] | None = None,
) -> list[dict[str, Any]]:
discovered: list[dict[str, Any]] = []
now = now or datetime.now(timezone.utc)

for child in sorted(workspace_root.iterdir(), key=lambda item: item.name.lower()):
if child.name.startswith(".") or not child.is_dir() or child.is_symlink():
continue
if _is_ignored_project_dir(child.name):
exclusion_reason = workspace_exclusion_reason(child.name)
if exclusion_reason is not None:
_record_exclusion(exclusion_counts, exclusion_reason)
continue
if _is_project_dir(child):
discovered.append(
Expand All @@ -121,7 +142,12 @@ def discover_workspace_projects(
continue
discovered.extend(
_discover_nested_projects(
child, workspace_root, catalog_data=catalog_data, now=now, depth=2
child,
workspace_root,
catalog_data=catalog_data,
now=now,
depth=2,
exclusion_counts=exclusion_counts,
)
)
return _dedupe_checkouts_by_origin(discovered)
Expand Down Expand Up @@ -175,6 +201,7 @@ def _discover_nested_projects(
catalog_data: dict[str, Any],
now: datetime,
depth: int,
exclusion_counts: dict[str, int] | None = None,
) -> list[dict[str, Any]]:
if depth <= 0:
return []
Expand All @@ -183,7 +210,9 @@ def _discover_nested_projects(
for child in sorted(root.iterdir(), key=lambda item: item.name.lower()):
if child.name.startswith(".") or not child.is_dir() or child.is_symlink():
continue
if _is_ignored_project_dir(child.name):
exclusion_reason = workspace_exclusion_reason(child.name)
if exclusion_reason is not None:
_record_exclusion(exclusion_counts, exclusion_reason)
continue
if _is_project_dir(child):
discovered.append(
Expand All @@ -192,7 +221,12 @@ def _discover_nested_projects(
continue
discovered.extend(
_discover_nested_projects(
child, workspace_root, catalog_data=catalog_data, now=now, depth=depth - 1
child,
workspace_root,
catalog_data=catalog_data,
now=now,
depth=depth - 1,
exclusion_counts=exclusion_counts,
)
)
return discovered
Expand Down
45 changes: 45 additions & 0 deletions tests/test_operator_os_seam_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ def test_contract_shadow_marks_legacy_lineage_unknown_without_failing(
"CL-PROD-001",
"CL-INP-001",
"CL-COUNT-001",
"CL-EXCL-001",
}


Expand Down Expand Up @@ -258,6 +259,50 @@ def test_contract_shadow_warns_for_carried_notion_older_than_48_hours(
)


def test_contract_shadow_fails_when_excluded_backup_leaks_into_projects(
tmp_path: Path,
) -> None:
truth, markdown = _passing_paths(tmp_path)
payload = json.loads(truth.read_text())
payload.update(
{
"producer": {},
"inputs": {
"notion": {"mode": "unavailable"},
"catalog": {"sha256": None},
},
"source_summary": {"attention_state_counts": {"decision-needed": 1}},
"rollups": {"decision": {"decision_needed_count": 1}},
"exclusions": {
"policy_version": "workspace_discovery.v1",
"counts": {},
},
"projects": [
{
"identity": {
"path": "Documents/Codex Backups/Wave 2R Post-Update"
},
"derived": {"attention_state": "decision-needed"},
}
],
}
)
truth.write_text(json.dumps(payload))

result = lint_operator_os_seams(
truth_path=truth,
markdown_paths=markdown,
contract_shadow=True,
catalog_path=tmp_path / "missing-catalog.yaml",
now=NOW,
)

assert not result.passed
finding = next(item for item in result.findings if item.check == "CL-EXCL-001")
assert finding.level == "fail"
assert "Codex Backups" in finding.detail


def test_schema_pin_mismatch_fails(tmp_path: Path) -> None:
truth, markdown = _passing_paths(tmp_path)
_write_truth(truth, schema_version="0.6.0")
Expand Down
4 changes: 4 additions & 0 deletions tests/test_portfolio_truth.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@ def test_truth_snapshot_respects_declared_and_derived_fields(
assert result.snapshot.derivation_policy_version == "portfolio_attention.v2"
assert result.snapshot.inputs["catalog"]["sha256"]
assert result.snapshot.inputs["notion"]["mode"] == "unavailable"
assert result.snapshot.exclusions == {
"policy_version": "workspace_discovery.v1",
"counts": {},
}
assert result.snapshot.source_summary["attention_state_counts"]["active-product"] == 1
assert result.snapshot.source_summary["attention_state_counts"]["parked"] == 1

Expand Down
17 changes: 16 additions & 1 deletion tests/test_portfolio_truth_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
_dedupe_checkouts_by_origin,
_is_ignored_project_dir,
discover_workspace_projects,
workspace_exclusion_reason,
)


Expand Down Expand Up @@ -96,6 +97,8 @@ def test_ignore_predicate_matches_transient_dirs() -> None:
assert _is_ignored_project_dir("NoGoPRJs")
assert _is_ignored_project_dir("auraforge-signed-smoke-export")
assert _is_ignored_project_dir("resume-evolver-tmp-1776063720")
assert _is_ignored_project_dir("Codex Backups")
assert workspace_exclusion_reason("Codex Backups") == "backup-container"


def test_ignore_predicate_keeps_real_projects() -> None:
Expand All @@ -107,6 +110,8 @@ def test_ignore_predicate_keeps_real_projects() -> None:
"resume-evolver", # the real repo, sans -tmp-<ts> suffix
"smoke-test-runner", # "smoke" but not "smoke-export"
"tmp-tools", # "tmp" but not the -tmp-<digits> clone pattern
"CodexBackupTool",
"BackupBuddy",
):
assert not _is_ignored_project_dir(name), name

Expand All @@ -121,10 +126,20 @@ def _project(*parts: str) -> None:
_project("NoGoPRJs", "app") # nested under ignored container -> skipped
_project("auraforge-signed-smoke-export", "foo-plan") # ignored bundle -> skipped
_project("resume-evolver-tmp-1776063720") # top-level tmp clone -> skipped
_project("Documents", "Codex Backups", "Wave 2R Post-Update", "README-fixture")
_project("Documents", "RealNestedProject")

exclusion_counts: dict[str, int] = {}
result = discover_workspace_projects(
tmp_path,
catalog_data={},
now=datetime(2026, 6, 2, tzinfo=timezone.utc),
exclusion_counts=exclusion_counts,
)
assert {p["name"] for p in result} == {"LegitProject"}
assert {p["name"] for p in result} == {"LegitProject", "RealNestedProject"}
assert exclusion_counts == {
"backup-container": 1,
"generated-evidence": 1,
"operator-excluded": 1,
"temporary-checkout": 1,
}