mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Own mobile relay runtime in L7
This commit is contained in:
@@ -3558,7 +3558,8 @@
|
||||
"lane": "L7",
|
||||
"contract": "docs/release-control/v6/internal/subsystems/relay-runtime.md",
|
||||
"owned_prefixes": [
|
||||
"internal/relay/"
|
||||
"internal/relay/",
|
||||
"pulse-mobile:src/relay/"
|
||||
],
|
||||
"owned_files": [
|
||||
"internal/config/persistence_relay.go"
|
||||
@@ -3568,7 +3569,15 @@
|
||||
"test_prefixes": [],
|
||||
"exact_files": [
|
||||
"internal/config/persistence_relay_test.go",
|
||||
"internal/relay/client_test.go"
|
||||
"internal/relay/client_test.go",
|
||||
"pulse-mobile:src/relay/__tests__/channel.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/client-hardening.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/client.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/encryption.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/identity.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/protocol-contract.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/protocol.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/proxy.test.ts"
|
||||
],
|
||||
"require_explicit_path_policy_coverage": true,
|
||||
"path_policies": [
|
||||
@@ -3597,6 +3606,26 @@
|
||||
"exact_files": [
|
||||
"internal/config/persistence_relay_test.go"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mobile-relay-runtime",
|
||||
"label": "mobile relay runtime proof",
|
||||
"match_prefixes": [
|
||||
"pulse-mobile:src/relay/"
|
||||
],
|
||||
"match_files": [],
|
||||
"allow_same_subsystem_tests": false,
|
||||
"test_prefixes": [],
|
||||
"exact_files": [
|
||||
"pulse-mobile:src/relay/__tests__/channel.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/client-hardening.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/client.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/encryption.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/identity.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/protocol-contract.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/protocol.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/proxy.test.ts"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -15,15 +15,19 @@
|
||||
|
||||
## Purpose
|
||||
|
||||
Own the desktop relay runtime, its persisted client configuration, and the
|
||||
canonical reconnect, encryption, and relay-trust behavior for Pulse instance
|
||||
bridging.
|
||||
Own the desktop and mobile relay runtimes, their persisted relay state
|
||||
boundaries, and the canonical reconnect, encryption, protocol, proxy, and
|
||||
relay-trust behavior for Pulse instance bridging.
|
||||
|
||||
## Canonical Files
|
||||
|
||||
1. `internal/relay/client.go`
|
||||
2. `internal/relay/protocol.go`
|
||||
3. `internal/config/persistence_relay.go`
|
||||
4. `pulse-mobile:src/relay/client.ts`
|
||||
5. `pulse-mobile:src/relay/protocol.ts`
|
||||
6. `pulse-mobile:src/relay/proxy.ts`
|
||||
7. `pulse-mobile:src/relay/encryption.ts`
|
||||
|
||||
## Shared Boundaries
|
||||
|
||||
@@ -34,7 +38,8 @@ bridging.
|
||||
1. Add or change desktop relay reconnect, registration, drain, proxy-stream, or encrypted channel behavior through `internal/relay/`
|
||||
2. Add or change relay control payload schemas, including mobile-visible push notification metadata, through `internal/relay/protocol.go`
|
||||
3. Add or change persisted relay enablement, server URL, or reconnect-safe default loading through `internal/config/persistence_relay.go`
|
||||
4. Keep desktop relay changes aligned with the governed mobile and server relay surfaces represented by the L7 lane evidence
|
||||
4. Add or change mobile relay reconnect, drain, channel, encryption, proxy, or identity behavior through `pulse-mobile:src/relay/`
|
||||
5. Keep desktop and mobile relay changes aligned with the governed server relay surfaces represented by the L7 lane evidence
|
||||
|
||||
## Forbidden Paths
|
||||
|
||||
@@ -47,12 +52,14 @@ bridging.
|
||||
1. Update this contract when new desktop relay runtime or persistence entry points become canonical
|
||||
2. Keep relay client changes tied to explicit runtime proof in `internal/relay/client_test.go`
|
||||
3. Keep persisted relay config loading tied to explicit proof in `internal/config/persistence_relay_test.go`
|
||||
4. Keep mobile relay runtime changes tied to explicit proof in `pulse-mobile:src/relay/__tests__/`
|
||||
|
||||
## Current State
|
||||
|
||||
The desktop relay client had been carrying real runtime behavior without any
|
||||
explicit subsystem ownership even though L7 already treats relay readiness as a
|
||||
governed release lane. This contract makes that desktop boundary explicit.
|
||||
The relay lane had been carrying real desktop and mobile runtime behavior
|
||||
without fully explicit subsystem ownership even though L7 already treats relay
|
||||
readiness as a governed release lane. This contract makes both relay-runtime
|
||||
boundaries explicit.
|
||||
The canonical relay runtime now includes local CA-bundle trust via
|
||||
`SSL_CERT_FILE`, so self-hosted relay deployments can use a private CA without
|
||||
forking the dial path or disabling TLS verification globally.
|
||||
@@ -86,3 +93,7 @@ the runtime can read legacy plaintext relay settings, it must rewrite
|
||||
canonical encrypted storage immediately on load instead of leaving
|
||||
`instance_secret` or the relay identity private key on disk as a normal
|
||||
runtime path.
|
||||
The mobile relay runtime is part of the same owned surface: reconnect drain
|
||||
failover hints are one-shot recovery instructions, not permanent relay URL
|
||||
overrides, so a successful failover reconnect must return future reconnects to
|
||||
the instance's canonical relay URL unless the server sends a fresh drain hint.
|
||||
|
||||
@@ -18,41 +18,65 @@ from canonical_completion_guard import (
|
||||
subsystem_matches_path,
|
||||
)
|
||||
|
||||
WORKSPACE_REPOS_ROOT = REPO_ROOT.parent
|
||||
|
||||
|
||||
def split_workspace_path(path: str) -> tuple[Path, str]:
|
||||
if ":" not in path:
|
||||
return REPO_ROOT, path
|
||||
repo_id, rel = path.split(":", 1)
|
||||
return WORKSPACE_REPOS_ROOT / repo_id, rel
|
||||
|
||||
|
||||
def qualify_workspace_path(repo_root: Path, rel: str) -> str:
|
||||
if repo_root == REPO_ROOT:
|
||||
return rel
|
||||
return f"{repo_root.name}:{rel}"
|
||||
|
||||
|
||||
def owned_runtime_files(rule: dict) -> list[str]:
|
||||
owned: set[str] = set()
|
||||
search_roots: set[Path] = set()
|
||||
|
||||
for prefix in rule.get("owned_prefixes", []):
|
||||
root = REPO_ROOT / prefix
|
||||
repo_root, rel_prefix = split_workspace_path(prefix.rstrip("/"))
|
||||
root = repo_root / rel_prefix
|
||||
if root.exists():
|
||||
search_roots.add(root if root.is_dir() else root.parent)
|
||||
continue
|
||||
|
||||
parent = root.parent
|
||||
while parent != REPO_ROOT and not parent.exists():
|
||||
while parent != repo_root and not parent.exists():
|
||||
parent = parent.parent
|
||||
if parent.exists():
|
||||
search_roots.add(parent)
|
||||
|
||||
for root in search_roots:
|
||||
repo_root = root
|
||||
while repo_root != WORKSPACE_REPOS_ROOT and repo_root.parent != WORKSPACE_REPOS_ROOT:
|
||||
repo_root = repo_root.parent
|
||||
if repo_root.parent != WORKSPACE_REPOS_ROOT:
|
||||
repo_root = REPO_ROOT
|
||||
for path in root.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
rel = path.relative_to(REPO_ROOT).as_posix()
|
||||
if is_test_or_fixture(rel) or is_ignored_runtime_file(rel):
|
||||
rel = path.relative_to(repo_root).as_posix()
|
||||
candidate = qualify_workspace_path(repo_root, rel)
|
||||
if is_test_or_fixture(candidate) or is_ignored_runtime_file(candidate):
|
||||
continue
|
||||
if subsystem_matches_path(rule, rel):
|
||||
owned.add(rel)
|
||||
if subsystem_matches_path(rule, candidate):
|
||||
owned.add(candidate)
|
||||
|
||||
for rel in rule.get("owned_files", []):
|
||||
path = REPO_ROOT / rel
|
||||
repo_root, repo_rel = split_workspace_path(rel)
|
||||
path = repo_root / repo_rel
|
||||
if not path.exists() or not path.is_file():
|
||||
continue
|
||||
if is_test_or_fixture(rel) or is_ignored_runtime_file(rel):
|
||||
candidate = qualify_workspace_path(repo_root, repo_rel)
|
||||
if is_test_or_fixture(candidate) or is_ignored_runtime_file(candidate):
|
||||
continue
|
||||
if subsystem_matches_path(rule, rel):
|
||||
owned.add(rel)
|
||||
if subsystem_matches_path(rule, candidate):
|
||||
owned.add(candidate)
|
||||
|
||||
return sorted(owned)
|
||||
|
||||
@@ -220,6 +244,42 @@ class CanonicalCompletionGuardTest(unittest.TestCase):
|
||||
],
|
||||
)
|
||||
|
||||
def test_mobile_relay_runtime_change_requires_relay_contract(self):
|
||||
required = infer_impacted_subsystems(["pulse-mobile:src/relay/client.ts"])
|
||||
self.assertEqual(set(required), {"relay-runtime"})
|
||||
|
||||
relay = required["relay-runtime"]
|
||||
self.assertEqual(
|
||||
relay["contract"],
|
||||
"docs/release-control/v6/internal/subsystems/relay-runtime.md",
|
||||
)
|
||||
self.assertEqual(
|
||||
relay["touched_runtime_files"],
|
||||
["pulse-mobile:src/relay/client.ts"],
|
||||
)
|
||||
self.assertEqual(
|
||||
relay["verification_requirements"],
|
||||
[
|
||||
{
|
||||
"id": "mobile-relay-runtime",
|
||||
"label": "mobile relay runtime proof",
|
||||
"touched_runtime_files": ["pulse-mobile:src/relay/client.ts"],
|
||||
"allow_same_subsystem_tests": False,
|
||||
"test_prefixes": [],
|
||||
"exact_files": [
|
||||
"pulse-mobile:src/relay/__tests__/channel.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/client-hardening.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/client.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/encryption.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/identity.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/protocol-contract.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/protocol.test.ts",
|
||||
"pulse-mobile:src/relay/__tests__/proxy.test.ts",
|
||||
],
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
def test_windows_install_script_change_uses_shared_installer_policies(self):
|
||||
required = infer_impacted_subsystems(["scripts/install.ps1"])
|
||||
self.assertEqual(set(required), {"agent-lifecycle", "deployment-installability"})
|
||||
|
||||
@@ -66,6 +66,7 @@ PATH_SUFFIXES = (
|
||||
".yaml",
|
||||
".yml",
|
||||
)
|
||||
WORKSPACE_REPOS_ROOT = REPO_ROOT.parent
|
||||
|
||||
|
||||
def sorted_casefold(values: list[str]) -> list[str]:
|
||||
@@ -107,17 +108,61 @@ def looks_like_repo_path(token: str) -> bool:
|
||||
return "/" in candidate or candidate.endswith(PATH_SUFFIXES)
|
||||
|
||||
|
||||
def validate_repo_path_token(token: str, *, rel: str, heading: str, errors: list[str]) -> None:
|
||||
def repo_roots_for_status(status_payload: dict[str, Any]) -> dict[str, Path]:
|
||||
active_repos = [
|
||||
repo_id
|
||||
for repo_id in status_payload.get("scope", {}).get("active_repos", [])
|
||||
if isinstance(repo_id, str) and repo_id.strip()
|
||||
]
|
||||
repo_roots = {REPO_ROOT.name: REPO_ROOT}
|
||||
for repo_id in active_repos:
|
||||
if repo_id == REPO_ROOT.name:
|
||||
continue
|
||||
repo_roots[repo_id] = WORKSPACE_REPOS_ROOT / repo_id
|
||||
return repo_roots
|
||||
|
||||
|
||||
def resolve_repo_path_token(token: str, *, repo_roots: dict[str, Path]) -> Path | None:
|
||||
raw = token.rstrip("/") if token.endswith("/") else token
|
||||
if not raw:
|
||||
errors.append(f"{rel} {heading} contains non-clean repo-relative path {token!r}")
|
||||
return
|
||||
return None
|
||||
repo_id = REPO_ROOT.name
|
||||
repo_rel = raw
|
||||
if ":" in raw:
|
||||
repo_id, repo_rel = raw.split(":", 1)
|
||||
if not repo_id:
|
||||
return None
|
||||
candidate = Path(raw)
|
||||
normalized = candidate.as_posix()
|
||||
if candidate.is_absolute() or raw.startswith("../") or "/../" in raw or normalized != raw:
|
||||
return None
|
||||
repo_candidate = Path(repo_rel)
|
||||
repo_normalized = repo_candidate.as_posix()
|
||||
if (
|
||||
repo_candidate.is_absolute()
|
||||
or repo_rel.startswith("../")
|
||||
or "/../" in repo_rel
|
||||
or repo_normalized != repo_rel
|
||||
):
|
||||
return None
|
||||
repo_root = repo_roots.get(repo_id)
|
||||
if repo_root is None:
|
||||
return None
|
||||
return repo_root / repo_rel
|
||||
|
||||
|
||||
def validate_repo_path_token(
|
||||
token: str,
|
||||
*,
|
||||
rel: str,
|
||||
heading: str,
|
||||
errors: list[str],
|
||||
repo_roots: dict[str, Path],
|
||||
) -> None:
|
||||
resolved = resolve_repo_path_token(token, repo_roots=repo_roots)
|
||||
if resolved is None:
|
||||
errors.append(f"{rel} {heading} contains non-clean repo-relative path {token!r}")
|
||||
return
|
||||
resolved = REPO_ROOT / raw
|
||||
if not resolved.exists():
|
||||
errors.append(f"{rel} {heading} references missing path {token!r}")
|
||||
return
|
||||
@@ -151,7 +196,12 @@ def parse_contract_metadata(body_lines: list[str]) -> tuple[dict[str, Any] | Non
|
||||
return payload, errors
|
||||
|
||||
|
||||
def audit_contract_text(rel: str, content: str) -> tuple[dict[str, Any], list[str]]:
|
||||
def audit_contract_text(
|
||||
rel: str,
|
||||
content: str,
|
||||
*,
|
||||
repo_roots: dict[str, Path],
|
||||
) -> tuple[dict[str, Any], list[str]]:
|
||||
errors: list[str] = []
|
||||
path_references: list[dict[str, str]] = []
|
||||
section_items: dict[str, list[str]] = {}
|
||||
@@ -200,13 +250,25 @@ def audit_contract_text(rel: str, content: str) -> tuple[dict[str, Any], list[st
|
||||
errors.append(f"{rel} section {heading!r} entries must include at least one repo path")
|
||||
continue
|
||||
for token in path_tokens:
|
||||
validate_repo_path_token(token, rel=rel, heading=heading, errors=errors)
|
||||
validate_repo_path_token(
|
||||
token,
|
||||
rel=rel,
|
||||
heading=heading,
|
||||
errors=errors,
|
||||
repo_roots=repo_roots,
|
||||
)
|
||||
path_references.append({"heading": heading, "path": token})
|
||||
if heading == "## Extension Points":
|
||||
for _, item in items:
|
||||
for token in re.findall(r"`([^`]+)`", item):
|
||||
if looks_like_repo_path(token):
|
||||
validate_repo_path_token(token, rel=rel, heading=heading, errors=errors)
|
||||
validate_repo_path_token(
|
||||
token,
|
||||
rel=rel,
|
||||
heading=heading,
|
||||
errors=errors,
|
||||
repo_roots=repo_roots,
|
||||
)
|
||||
path_references.append({"heading": heading, "path": token})
|
||||
|
||||
return {
|
||||
@@ -284,6 +346,7 @@ def audit_contract_payload(
|
||||
for subsystem in registry_subsystems
|
||||
if isinstance(subsystem, dict) and isinstance(subsystem.get("contract"), str)
|
||||
}
|
||||
repo_roots = repo_roots_for_status(status_payload)
|
||||
expected_subsystem_ids = {
|
||||
str(subsystem.get("id", "")).strip()
|
||||
for subsystem in registry_subsystems
|
||||
@@ -302,7 +365,7 @@ def audit_contract_payload(
|
||||
seen_subsystem_ids: set[str] = set()
|
||||
|
||||
for rel in sorted(actual_contracts):
|
||||
parsed, parse_errors = audit_contract_text(rel, contract_texts[rel])
|
||||
parsed, parse_errors = audit_contract_text(rel, contract_texts[rel], repo_roots=repo_roots)
|
||||
errors.extend(parse_errors)
|
||||
metadata = parsed.get("metadata")
|
||||
subsystem = expected_contracts.get(rel)
|
||||
@@ -420,7 +483,13 @@ def audit_contract_payload(
|
||||
)
|
||||
continue
|
||||
shared_path = path_tokens[0]
|
||||
validate_repo_path_token(shared_path, rel=rel, heading="## Shared Boundaries", errors=errors)
|
||||
validate_repo_path_token(
|
||||
shared_path,
|
||||
rel=rel,
|
||||
heading="## Shared Boundaries",
|
||||
errors=errors,
|
||||
repo_roots=repo_roots,
|
||||
)
|
||||
actual_shared_paths.append(shared_path)
|
||||
if shared_path in seen_shared_paths:
|
||||
errors.append(
|
||||
|
||||
@@ -295,6 +295,76 @@ Canonical alert identity is live runtime truth.
|
||||
"\n".join(report["errors"]),
|
||||
)
|
||||
|
||||
def test_audit_contract_payload_accepts_cross_repo_contract_paths(self) -> None:
|
||||
registry_payload = {
|
||||
"subsystems": [
|
||||
{
|
||||
"id": "relay-runtime",
|
||||
"lane": "L7",
|
||||
"contract": "docs/release-control/v6/internal/subsystems/relay-runtime.md",
|
||||
}
|
||||
]
|
||||
}
|
||||
status_payload = {
|
||||
"scope": {
|
||||
"active_repos": ["pulse", "pulse-mobile"],
|
||||
},
|
||||
"lanes": [{"id": "L7"}],
|
||||
}
|
||||
contract_texts = {
|
||||
"docs/release-control/v6/internal/subsystems/relay-runtime.md": """# Relay Runtime Contract
|
||||
|
||||
## Contract Metadata
|
||||
|
||||
```json
|
||||
{
|
||||
"subsystem_id": "relay-runtime",
|
||||
"lane": "L7",
|
||||
"contract_file": "docs/release-control/v6/internal/subsystems/relay-runtime.md",
|
||||
"status_file": "docs/release-control/v6/internal/status.json",
|
||||
"registry_file": "docs/release-control/v6/internal/subsystems/registry.json",
|
||||
"dependency_subsystem_ids": []
|
||||
}
|
||||
```
|
||||
|
||||
## Purpose
|
||||
|
||||
Own relay runtime truth.
|
||||
|
||||
## Canonical Files
|
||||
|
||||
1. `pulse-mobile:src/relay/client.ts`
|
||||
|
||||
## Shared Boundaries
|
||||
|
||||
1. None.
|
||||
|
||||
## Extension Points
|
||||
|
||||
1. Add mobile relay reconnect behavior through `pulse-mobile:src/relay/`
|
||||
|
||||
## Forbidden Paths
|
||||
|
||||
1. Ad hoc mobile relay reconnect state.
|
||||
|
||||
## Completion Obligations
|
||||
|
||||
1. Keep mobile relay runtime changes tied to tests.
|
||||
|
||||
## Current State
|
||||
|
||||
Cross-repo relay ownership is explicit.
|
||||
""",
|
||||
}
|
||||
|
||||
report = audit_contract_payload(
|
||||
registry_payload=registry_payload,
|
||||
status_payload=status_payload,
|
||||
contract_texts=contract_texts,
|
||||
)
|
||||
|
||||
self.assertEqual(report["errors"], [])
|
||||
|
||||
def test_audit_contract_payload_rejects_missing_extension_point_reference(self) -> None:
|
||||
registry_payload = {
|
||||
"subsystems": [
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
@@ -61,20 +62,35 @@ def sorted_casefold(values: list[str]) -> list[str]:
|
||||
|
||||
|
||||
def tracked_repo_files() -> set[str]:
|
||||
result = subprocess.run(
|
||||
["git", "ls-files", "-z"],
|
||||
cwd=REPO_ROOT,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=False,
|
||||
)
|
||||
files = {
|
||||
entry.decode("utf-8")
|
||||
for entry in result.stdout.split(b"\x00")
|
||||
if entry
|
||||
}
|
||||
return tracked_workspace_files(active_repos=[REPO_ROOT.name], local_repo=REPO_ROOT.name)
|
||||
|
||||
|
||||
def tracked_workspace_files(*, active_repos: list[str], local_repo: str) -> set[str]:
|
||||
files: set[str] = set()
|
||||
repos_root = REPO_ROOT.parent
|
||||
for repo_id in active_repos:
|
||||
repo_root = REPO_ROOT if repo_id == local_repo else repos_root / repo_id
|
||||
if not repo_root.exists():
|
||||
continue
|
||||
env = os.environ.copy()
|
||||
if repo_root != REPO_ROOT:
|
||||
env.pop("GIT_INDEX_FILE", None)
|
||||
result = subprocess.run(
|
||||
["git", "ls-files", "-z"],
|
||||
cwd=repo_root,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=False,
|
||||
env=env,
|
||||
)
|
||||
for entry in result.stdout.split(b"\x00"):
|
||||
if not entry:
|
||||
continue
|
||||
rel = entry.decode("utf-8")
|
||||
files.add(rel if repo_id == local_repo else f"{repo_id}:{rel}")
|
||||
|
||||
# Governance files are filesystem-only (gitignored). Supplement with any
|
||||
# files that exist on disk under the subsystems/contracts directory so that
|
||||
# files that exist on disk under the control-plane contracts directory so
|
||||
# contract path references in the registry resolve correctly.
|
||||
contracts_dir = Path(DEFAULT_CONTROL_PLANE["subsystems_dir_path"])
|
||||
if contracts_dir.exists():
|
||||
@@ -612,6 +628,12 @@ def render_pretty(report: dict[str, Any]) -> str:
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = parse_args(list(argv or []))
|
||||
status_payload = load_status_payload(staged=args.staged)
|
||||
scope = status_payload.get("scope", {})
|
||||
active_repos = [
|
||||
repo_id
|
||||
for repo_id in scope.get("active_repos", [])
|
||||
if isinstance(repo_id, str) and repo_id.strip()
|
||||
] or [REPO_ROOT.name]
|
||||
lane_ids = {
|
||||
lane.get("id")
|
||||
for lane in status_payload.get("lanes", [])
|
||||
@@ -619,7 +641,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
}
|
||||
report = audit_registry_payload(
|
||||
load_registry_payload(staged=args.staged),
|
||||
tracked_files=tracked_repo_files(),
|
||||
tracked_files=tracked_workspace_files(active_repos=active_repos, local_repo=REPO_ROOT.name),
|
||||
status_lane_ids=lane_ids,
|
||||
schema_contract=registry_schema_contract(staged=args.staged),
|
||||
)
|
||||
|
||||
@@ -53,6 +53,52 @@ class RegistryAuditTest(unittest.TestCase):
|
||||
self.assertEqual(report["summary"]["shared_ownership_count"], 0)
|
||||
self.assertEqual(report["subsystems"][0]["default_fallback_count"], 0)
|
||||
|
||||
def test_audit_registry_payload_accepts_cross_repo_owned_prefixes(self) -> None:
|
||||
payload = {
|
||||
"version": 12,
|
||||
"shared_ownerships": [],
|
||||
"subsystems": [
|
||||
{
|
||||
"id": "relay-runtime",
|
||||
"lane": "L7",
|
||||
"contract": "docs/release-control/v6/internal/subsystems/relay-runtime.md",
|
||||
"owned_prefixes": ["pulse-mobile:src/relay/"],
|
||||
"owned_files": [],
|
||||
"verification": {
|
||||
"allow_same_subsystem_tests": True,
|
||||
"test_prefixes": [],
|
||||
"exact_files": [
|
||||
"pulse-mobile:src/relay/__tests__/client.test.ts",
|
||||
],
|
||||
"require_explicit_path_policy_coverage": True,
|
||||
"path_policies": [
|
||||
{
|
||||
"id": "mobile-relay-runtime",
|
||||
"label": "mobile relay runtime proof",
|
||||
"match_prefixes": ["pulse-mobile:src/relay/"],
|
||||
"match_files": [],
|
||||
"allow_same_subsystem_tests": False,
|
||||
"test_prefixes": [],
|
||||
"exact_files": [
|
||||
"pulse-mobile:src/relay/__tests__/client.test.ts",
|
||||
],
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
tracked_files = {
|
||||
"docs/release-control/v6/internal/subsystems/relay-runtime.md",
|
||||
"pulse-mobile:src/relay/__tests__/client.test.ts",
|
||||
"pulse-mobile:src/relay/client.ts",
|
||||
}
|
||||
|
||||
report = audit_registry_payload(payload, tracked_files=tracked_files, status_lane_ids={"L7"})
|
||||
|
||||
self.assertEqual(report["errors"], [])
|
||||
self.assertEqual(report["subsystems"][0]["owned_runtime_file_count"], 1)
|
||||
|
||||
def test_audit_registry_payload_flags_unknown_lane_and_missing_contract(self) -> None:
|
||||
payload = {
|
||||
"version": 12,
|
||||
|
||||
@@ -23,13 +23,27 @@ from canonical_completion_guard import (
|
||||
from status_audit import audit_status_payload, load_status_payload
|
||||
from registry_audit import load_registry_payload
|
||||
|
||||
WORKSPACE_REPOS_ROOT = REPO_ROOT.parent
|
||||
|
||||
|
||||
def normalize_input_path(raw: str) -> str:
|
||||
candidate = Path(raw.strip())
|
||||
if candidate.is_absolute():
|
||||
candidate = candidate.resolve()
|
||||
try:
|
||||
candidate = candidate.resolve().relative_to(REPO_ROOT)
|
||||
candidate = candidate.relative_to(REPO_ROOT)
|
||||
except ValueError:
|
||||
try:
|
||||
repo_relative = candidate.relative_to(WORKSPACE_REPOS_ROOT)
|
||||
except ValueError:
|
||||
return candidate.as_posix()
|
||||
parts = repo_relative.parts
|
||||
if len(parts) >= 2:
|
||||
repo_id = parts[0]
|
||||
rel = Path(*parts[1:]).as_posix()
|
||||
if repo_id == REPO_ROOT.name:
|
||||
return rel
|
||||
return f"{repo_id}:{rel}"
|
||||
return candidate.as_posix()
|
||||
return candidate.as_posix()
|
||||
|
||||
|
||||
@@ -62,6 +62,24 @@ class SubsystemLookupTest(unittest.TestCase):
|
||||
result = lookup_paths(["README.md"])
|
||||
self.assertEqual(result["unowned_runtime_files"], ["README.md"])
|
||||
|
||||
def test_lookup_paths_normalizes_cross_repo_absolute_runtime_paths(self) -> None:
|
||||
result = lookup_paths([str(REPO_ROOT.parent / "pulse-mobile" / "src/relay/client.ts")])
|
||||
self.assertEqual(result["unowned_runtime_files"], [])
|
||||
self.assertEqual(
|
||||
{item["subsystem"] for item in result["impacted_subsystems"]},
|
||||
{"relay-runtime"},
|
||||
)
|
||||
file_entry = result["files"][0]
|
||||
self.assertEqual(file_entry["path"], "pulse-mobile:src/relay/client.ts")
|
||||
self.assertEqual(file_entry["classification"], "runtime")
|
||||
self.assertEqual(
|
||||
{match["subsystem"] for match in file_entry["matches"]},
|
||||
{"relay-runtime"},
|
||||
)
|
||||
match = file_entry["matches"][0]
|
||||
self.assertEqual(match["lane_context"]["lane_id"], "L7")
|
||||
self.assertEqual(match["verification_requirement"]["id"], "mobile-relay-runtime")
|
||||
|
||||
def test_lookup_paths_assigns_shared_tag_badges_to_frontend_primitives(self) -> None:
|
||||
result = lookup_paths(["frontend-modern/src/components/shared/TagBadges.tsx"])
|
||||
self.assertEqual(result["unowned_runtime_files"], [])
|
||||
|
||||
Reference in New Issue
Block a user