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
28 changes: 26 additions & 2 deletions scripts/shadow_root_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -2620,12 +2620,36 @@ def seat_board_entities(
return ({candidate["id"]} if candidate is not None else set()), 0


_IDENTITY_QUARANTINE_NOTICED: set[str] = set()


def _identity_index(payload: dict) -> dict[str, list[dict]]:
"""Index live identities once; stored ids are fallback only for missing plans."""
"""Index live identities once; stored ids are fallback for an unreadable plan."""
result: dict[str, list[dict]] = {}
for entity in payload["entities"]:
pointer = Path(entity["plan"])
identity = entity_id(pointer) if regular_plan(pointer) else entity["id"]
if not regular_plan(pointer):
identity = entity["id"]
else:
try:
identity = entity_id(pointer)
except BoardError:
# Quarantine, never blank the board: one pointer whose Git
# identity cannot be read (a TCC-protected directory) must not
# refuse the refresh for every healthy peer. The entity keeps
# its stored id this cycle and the operator is told why, once.
# The notice names the entity by its path-free stored id and a
# fixed reason: plan paths are never echoed (privacy law), and
# the raw exception text may carry Git output.
stored_id = str(entity["id"])
if stored_id not in _IDENTITY_QUARANTINE_NOTICED:
_IDENTITY_QUARANTINE_NOTICED.add(stored_id)
print(
"shadow: entity " + stored_id[:12] + " quarantined from "
"the identity index: project Git identity could not be read",
file=sys.stderr,
)
identity = entity["id"]
result.setdefault(identity, []).append(entity)
return result

Expand Down
119 changes: 119 additions & 0 deletions tests/test_root_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -5850,6 +5850,125 @@ def test_a_transient_origin_read_degrades_to_the_digest_shape(self) -> None:
self.assertTrue(locator.endswith("/PLAN.md"), locator)


class OneUnreadablePointerNeverBlanksThePortfolio(unittest.TestCase):
"""Measured 2026-09-16: one registered pointer under a TCC-protected
~/Documents path made `git rev-parse` fail "Operation not permitted", so
`plan_identity_parts` raised and `_identity_index` refused for all 46
entities. The docstring already names stored ids as the fallback for a
missing plan; an unreadable plan is the same quarantine, not a board-wide
outage.
"""

def setUp(self) -> None:
board_api._IDENTITY_QUARANTINE_NOTICED.clear()
self.addCleanup(board_api._IDENTITY_QUARANTINE_NOTICED.clear)

def payload_with_an_unreadable_pointer(self, root: Path) -> tuple[dict, str, str]:
readable = project(root, name="readable") / "PLAN.md"
unreadable = project(root, name="unreadable") / "PLAN.md"
readable_id = board_api.entity_id(readable)
unreadable_id = board_api.entity_id(unreadable)
payload = {
"schema": "shadow.root-board.v1",
"revision": 7,
"projects": [
{"id": "readable", "priority": 1},
{"id": "unreadable", "priority": 2},
],
"entities": [
{
"id": readable_id,
"project": "readable",
"plan": str(readable),
"resume": "~aa11",
},
{
"id": unreadable_id,
"project": "unreadable",
"plan": str(unreadable),
"resume": "~aa11",
},
],
"claims": [],
}
return payload, readable_id, unreadable_id

def refusing_entity_id(self, blocked: str):
"""The TCC shape: the file reads as regular, the Git identity does not."""
real = board_api.entity_id

def reader(plan: Path) -> str:
if str(plan) == blocked:
raise board_api.BoardError(
"project Git identity could not be read; retry when Git is available"
)
return real(plan)

return reader

def test_one_unreadable_pointer_keeps_every_other_entity_indexed(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
payload, readable_id, unreadable_id = self.payload_with_an_unreadable_pointer(
Path(tmp)
)
blocked = payload["entities"][1]["plan"]
stderr = io.StringIO()
with (
mock.patch.object(
board_api, "entity_id", side_effect=self.refusing_entity_id(blocked)
),
redirect_stderr(stderr),
):
index = board_api._identity_index(payload)

self.assertEqual(set(index), {readable_id, unreadable_id})
self.assertEqual(
[entity["plan"] for entity in index[unreadable_id]], [blocked]
)
notice = stderr.getvalue()
self.assertIn("quarantined", notice)
self.assertIn(unreadable_id[:12], notice)
self.assertNotIn(str(blocked), notice) # plan paths are never echoed

def test_the_quarantine_notice_is_printed_once_per_pointer(self) -> None:
# `_identity_index` is rebuilt by `_entity_aliases` on every lookup;
# a per-call print would repeat the notice once per entity per command.
with tempfile.TemporaryDirectory() as tmp:
payload, _, _ = self.payload_with_an_unreadable_pointer(Path(tmp))
blocked = payload["entities"][1]["plan"]
stderr = io.StringIO()
with (
mock.patch.object(
board_api, "entity_id", side_effect=self.refusing_entity_id(blocked)
),
redirect_stderr(stderr),
):
board_api._identity_index(payload)
board_api._identity_index(payload)

self.assertEqual(stderr.getvalue().count("quarantined"), 1)

def test_the_registered_locator_index_still_answers_for_every_entity(self) -> None:
# `registered_locator_index` is the reader the portfolio refresh calls;
# it must not refuse for 45 healthy entities because one is unreadable.
with tempfile.TemporaryDirectory() as tmp:
payload, readable_id, unreadable_id = self.payload_with_an_unreadable_pointer(
Path(tmp)
)
blocked = payload["entities"][1]["plan"]
with (
mock.patch.object(board_api, "snapshot", return_value=payload),
mock.patch.object(
board_api, "entity_id", side_effect=self.refusing_entity_id(blocked)
),
redirect_stderr(io.StringIO()),
):
locators = board_api.registered_locator_index()

self.assertEqual(set(locators), {readable_id, unreadable_id})
self.assertEqual(locators[unreadable_id], (Path(blocked),))


class ADegradedPortfolioReadNamesItsTrueCause(unittest.TestCase):
"""Name the fault that was measured, and never print a stale board as current.

Expand Down
Loading