fix(marker): fix inheritance hook, topology load, update sync, and asdict

Bugs found via end-to-end testing of project-level marker definitions:

1. New links didn't inherit — apply_defs_to_new_link is async but was
   called without await in UDPLink.create().

2. Project load crashed — load_project passed marker_definitions to
   Project.__init__. Now popped in load_project and restored separately
   in Project.open() (it backs a read-only property).

3. PUT on a definition didn't sync to links — update_marker's guard
   rejected even the project-layer sync call. Added an `inherited`
   bypass flag used by update_marker_definition.

4. _ubridge_add_marker_filter raised re.PatternError — the name regex
   used (?i)(?!global) look-around, invalid in Python's re module.
   Dropped the prefix check there: "global-*" names are legitimate at
   the uBridge boundary (inherited definitions); forbidden only at the
   user-facing schema.

5. GET /links hid inherited markers — asdict()'s runtime branch used
   _persist_markers() (which filters inherited markers). Restored
   self._markers for the runtime branch; only the topology_dump branch
   filters (inherited markers are rebuilt from definitions on load).

6. Duplicate "already exists" warnings on project open — open() fanned
   out definitions to all links, but UDPLink.create() had already done
   so via its inheritance hook. Removed the redundant fan-out in open().
This commit is contained in:
YueGuobin committed 2026-07-16 00:39:52 +08:00
1 parent 84845f8504
commit 179f072c33
5 files changed
+23 -11

No files matched your search

+5 -3
View File
@@ -147,7 +147,7 @@ class UDPLink(Link):
self._created = True
# New links automatically inherit every active project-level marker
# definition so the user doesn't have to reconfigure.
self._project.apply_defs_to_new_link(self)
await self._project.apply_defs_to_new_link(self)
async def update(self):
"""
@@ -391,7 +391,7 @@ class UDPLink(Link):
self._project.emit_notification("link.updated", self.asdict())
self._project.dump()
async def update_marker(self, name, bpf=None, tag=None, enabled=None, color=None):
async def update_marker(self, name, bpf=None, tag=None, enabled=None, color=None, inherited=False):
"""
Update an existing marker's BPF/tag/enabled/color. Any change pushes via
``self.update()``; uBridge picks up the new params on the next NIO
@@ -402,13 +402,15 @@ class UDPLink(Link):
:param tag: new tag id (None = keep existing)
:param enabled: toggle (None = keep existing)
:param color: new hex color (None = keep existing)
:param inherited: set by project-level sync to bypass the inheritance
guard (the project layer is the legitimate editor)
"""
marker_info = self._markers.get(name)
if not marker_info:
raise ControllerNotFoundError(f"Marker '{name}' not found on link {self._id}")
if marker_info.get("inherited_from"):
if marker_info.get("inherited_from") and not inherited:
raise ControllerError(
f"Marker '{name}' is inherited from the project-level "
f"definition '{marker_info['inherited_from']}'. "