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
5 changes: 5 additions & 0 deletions .github/checks-manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@ checks:
lanes: ["local", "ci"]
platforms: ["macos"]
reason: "desktop Swift sources changed; compare against the pinned macOS formatter before requiring a flow cover"
- id: desktop-flow-contract-tests
command: ["python3", "desktop/macos/tests/test_desktop_flow_contract.py"]
triggers: ["desktop/macos/scripts/desktop_flow_contract.py", "desktop/macos/tests/test_desktop_flow_contract.py", "desktop/macos/Desktop/Sources/DesktopAutomationBridge.swift", ".github/checks-manifest.yaml"]
lanes: ["local", "ci"]
reason: "bridge actions moved into DesktopAutomationBridge+Notifications.swift while the action-source list still named only the base file, so desktop-flow-lint called their flows unknown and failed the push gate on every branch"
- id: desktop-grdb-insert-idiom-tests
command: ["python3", "desktop/macos/tests/test_check_grdb_insert_idiom.py"]
triggers: ["desktop/macos/scripts/check-grdb-insert-idiom.py", "desktop/macos/tests/test_check_grdb_insert_idiom.py", ".github/checks-manifest.yaml"]
Expand Down
37 changes: 28 additions & 9 deletions desktop/macos/scripts/desktop_flow_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,37 @@

from __future__ import annotations

from pathlib import Path

_DESKTOP_MACOS_DIR = Path(__file__).resolve().parent.parent
_SOURCES_DIR = _DESKTOP_MACOS_DIR / "Desktop" / "Sources"


def _bridge_action_sources() -> tuple[str, ...]:
"""Every `DesktopAutomationBridge*.swift`, found rather than listed.

Naming the base file alone is what let this list go stale: bridge actions moved into
`DesktopAutomationBridge+Notifications.swift`, nobody added it here, and the lint then
reported the flows that used them as referencing unknown actions — failing the push gate
on every branch while the actions themselves were registered correctly all along.

Discovery is scoped to this one filename prefix on purpose. The registration pattern the
lint matches (`name: "…"`) is common enough to appear in `APIClient`, `AuthService` and
`ChatBubble`, so scanning the whole tree would feed the lint action names that are not
actions. The prefix belongs to the bridge and nothing else.
"""
return tuple(
sorted(
path.relative_to(_DESKTOP_MACOS_DIR).as_posix()
for path in _SOURCES_DIR.glob("DesktopAutomationBridge*.swift")
)
)


# Paths are relative to desktop/macos. Keep the flow lint reader and CI impact
# resolver on this single list so an added bridge action cannot skip its flow
# validation route.
ACTION_SOURCE_RELATIVE_PATHS = (
"Desktop/Sources/DesktopAutomationBridge.swift",
# The bridge is split across extension files. Each one registers actions the
# flow lint must be able to see; omitting one makes its actions look unknown
# and reports a valid flow as broken, which is what happened to
# notifications-settings.yaml when +Notifications.swift was factored out.
# main fixed +Notifications independently; +ChatFirst had the same gap.
"Desktop/Sources/DesktopAutomationBridge+Notifications.swift",
"Desktop/Sources/DesktopAutomationBridge+ChatFirst.swift",
ACTION_SOURCE_RELATIVE_PATHS = _bridge_action_sources() + (
"Desktop/Sources/FloatingControlBar/RealtimeHubController.swift",
"Desktop/Sources/Rewind/Core/RewindArtifactGauntlet.swift",
"Desktop/Sources/DesktopAutomationOpenOmiShortcutQA.swift",
Expand Down
61 changes: 61 additions & 0 deletions desktop/macos/tests/test_desktop_flow_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""Prove the flow contract sees every bridge action source.

`desktop_flow_contract` exists so "an added bridge action cannot skip its flow validation
route" — its own words. It failed at exactly that: three actions moved into
`DesktopAutomationBridge+Notifications.swift`, the hand-maintained list still named only the
base file, and `desktop-flow-lint` reported the flows using them as referencing unknown
actions. That failed the pre-push gate on every branch cut from main, on a file the pusher
never touched, while the actions were registered correctly the whole time.
"""

import pathlib
import re
import sys
import unittest

SCRIPTS = pathlib.Path(__file__).resolve().parents[1] / "scripts"
sys.path.insert(0, str(SCRIPTS))

from desktop_flow_contract import ACTION_SOURCE_RELATIVE_PATHS # noqa: E402

DESKTOP_MACOS = SCRIPTS.parent
SOURCES = DESKTOP_MACOS / "Desktop" / "Sources"
ACTION_NAME = re.compile(r'name:\s*"([^"]+)"')


class DesktopFlowContractTests(unittest.TestCase):
def test_every_bridge_file_on_disk_is_an_action_source(self):
"""The regression: a bridge file present but unlisted is invisible to the lint."""
on_disk = {
path.relative_to(DESKTOP_MACOS).as_posix()
for path in SOURCES.glob("DesktopAutomationBridge*.swift")
}
self.assertTrue(on_disk, "expected at least one DesktopAutomationBridge source")
self.assertLessEqual(
on_disk,
set(ACTION_SOURCE_RELATIVE_PATHS),
"a bridge source is not an action source, so the flows using its actions will "
"be reported as referencing unknown actions",
)

def test_the_notifications_extension_actions_are_reachable(self):
"""The two names that were actually reported unknown, pinned by value."""
registered: set[str] = set()
for relative in ACTION_SOURCE_RELATIVE_PATHS:
path = DESKTOP_MACOS / relative
if path.is_file():
registered.update(ACTION_NAME.findall(path.read_text(encoding="utf-8")))
for action in ("settings_notifications_snapshot", "set_notification_settings"):
self.assertIn(action, registered)

def test_listed_sources_all_exist(self):
"""Discovery must not invent a path: the lint fails hard on a missing source."""
for relative in ACTION_SOURCE_RELATIVE_PATHS:
self.assertTrue(
(DESKTOP_MACOS / relative).is_file(), f"listed action source is missing: {relative}"
)


if __name__ == "__main__":
unittest.main()
Loading