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
6 changes: 6 additions & 0 deletions config/notion-project-map.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
"portfolio-index": {
"localProjectId": "378c21f1-caf0-814e-b127-ddbfe578e1e5"
},
"operating-memory-os": {
"localProjectId": "3a3c21f1-caf0-81ec-943f-ed25069652e2"
},
"operator-scripts": {
"localProjectId": "3a3c21f1-caf0-810e-b671-d7cba2618f41"
},
"portfolio-mcp": {
"localProjectId": "38dc21f1-caf0-8157-9f5c-d6425a4b32b9"
},
Expand Down
8 changes: 8 additions & 0 deletions config/project-registry-overrides.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@
"lifecycle_state": "active",
"note": "Local operator control plane (127.0.0.1:46210). Most active project in bridge-db yet absent from auditor portfolio-truth."
},
{
"canonical_key": "operator-scripts",
"display_name": "operator-scripts",
"repo_full_name": "saagpatel/operator-scripts",
Comment thread
saagpatel marked this conversation as resolved.
"group_key": "operator_infra",
"lifecycle_state": "active",
"note": "Private scheduled-job scripts repository rooted at /Users/d/scripts, outside the default /Users/d/Projects audit scan."
},
{
"canonical_key": "supp:SecondBrain",
"display_name": "SecondBrain",
Expand Down
23 changes: 17 additions & 6 deletions src/operator_os_seam_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
BRIDGE_CANONICAL_KEY_DISAGREEMENTS,
DEFAULT_NOTION_PROJECTION_ONLY_ROWS,
DEFAULT_NOTION_TITLE_ALIASES,
DEFAULT_SUPPLEMENTARY,
IDENTITY_ALIAS_MAP,
IDENTITY_ALIAS_MAP_DEPRECATES_AFTER,
load_overrides_config,
normalize,
supp_key_for,
)
Expand All @@ -38,6 +38,9 @@
DEFAULT_NOTION_SNAPSHOT_PATH = Path(
"~/.local/share/notion-os/project-snapshot.json"
).expanduser()
DEFAULT_PROJECT_REGISTRY_OVERRIDES_PATH = (
Path(__file__).resolve().parents[1] / "config" / "project-registry-overrides.json"
)

HEX_FRAGMENT_RE = re.compile(r"^[0-9a-fA-F]{3}$")
EXPLICIT_UNRESOLVED_IDENTITIES = {"homeadhoc", "unresolved"}
Expand Down Expand Up @@ -792,14 +795,22 @@ def _build_identity_resolver(truth: dict[str, Any]) -> dict[str, str]:
if supp:
for value in (identity.get("display_name"), project_key, supp):
_add_identity_alias(resolver, value, supp)
# Supplementary registry projects (repo-less operator-OS projects the
# auditor's portfolio-truth does not track, e.g. personal-ops, SecondBrain)
# carry a supp: canonical_key. Seed them so they resolve like any project.
for supp_entry in DEFAULT_SUPPLEMENTARY:
# Supplementary registry projects (operator-OS projects the auditor's
# portfolio-truth does not track) must come from the same operator-owned
# config consumed by the registry generator. Falling back to built-ins
# preserves degraded operation when that config is absent.
_, supplementary, *_ = load_overrides_config(
DEFAULT_PROJECT_REGISTRY_OVERRIDES_PATH
)
for supp_entry in supplementary:
canonical = supp_entry.get("canonical_key")
if not isinstance(canonical, str) or not canonical:
continue
for value in (supp_entry.get("display_name"), canonical):
for value in (
supp_entry.get("display_name"),
supp_entry.get("repo_full_name"),
canonical,
):
_add_identity_alias(resolver, value, canonical)
# Notion title aliases (e.g. "OrbitForge (staging)" -> OrbitForge): resolve
# the alias target to its canonical, then map the drifted title onto it so
Expand Down
22 changes: 22 additions & 0 deletions tests/test_operator_os_seam_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,28 @@ def test_identity_resolution_supplementary_project_resolves(tmp_path: Path) -> N
assert result.passed, [f.detail for f in result.findings]


def test_identity_resolution_configured_supplementary_repo_resolves(
tmp_path: Path,
) -> None:
# operator-scripts is enrolled only in the operator-owned supplementary
# config, not the built-in fallback. Both bridge identity dialects must
# resolve through the same source used by the registry generator.
truth, markdown = _passing_paths(tmp_path)
_write_identity_truth(truth)
bridge_db = tmp_path / "bridge.db"
_write_bridge_db(
bridge_db,
activity_rows=[
("operator-scripts", "saagpatel/operator-scripts"),
("saagpatel/operator-scripts", "saagpatel/operator-scripts"),
],
)
result = lint_operator_os_seams(
truth_path=truth, markdown_paths=markdown, bridge_db_path=bridge_db, now=NOW
)
assert result.passed, [f.detail for f in result.findings]


def test_identity_resolution_migration_drift_alias_resolves(tmp_path: Path) -> None:
# o2-fable-runpack is OPERANT activity logged under a loose name; the
# census-seeded alias map maps it to the operant repo so it resolves.
Expand Down