ADR-0021: Canonical source as the WM's internal action identifier after runner initialization¶
- Status: accepted
- Date: 2026-04-14
- Deciders: @Aksem
- Tags: actions, wm, er, architecture
Context¶
ADR-0019 establishes that
external callers identify actions by import-path aliases. Internally the WM also holds a
canonical_source field on each Action domain object — the fully qualified class path derived
from cls.__module__ + "." + cls.__qualname__.
Canonical sources are resolved by the ER after finecodeRunner/updateConfig via
finecodeRunner/resolveActionMeta. The original protocol returned a sparse map: only
entries where the alias differed from the canonical were included. The WM inferred that absent
entries meant "alias == canonical" and left canonical_source = None for those actions.
This created a dual-lookup pattern throughout the WM (e.g. a.source == x or (a.canonical_source
is not None and a.canonical_source == x)): code had to accommodate both the pre-resolution
state (canonical_source is None) and the post-resolution state where the alias and canonical
might differ. The result was scattered defensive logic and an unclear invariant about when
canonical_source could be trusted.
Related ADRs Considered¶
- ADR-0019 — this ADR is its internal complement: ADR-0019 governs external string identifiers; this ADR governs the internal identifier the WM uses after runner initialization.
- ADR-0006 — type-level identity rule; canonical source is the string projection of that rule inside the WM.
Decision¶
The ER returns a complete (non-sparse) map from resolveActionMeta, including every
action whose class can be imported — even those where the alias already equals the canonical
path. Actions that fail to import are omitted (import failure = cannot run = no canonical to
report).
After update_runner_config completes, canonical_source is the WM's sole internal
identifier for each action whose class was successfully imported. No lookup code in the WM
may match an action by its config source for internal dispatch purposes; it must match by
canonical_source.
The only allowed exception is the external API boundary (find_action_by_source), which
accepts an import-path alias from callers and must still resolve it to an action — it does so by
matching a.source == alias to retrieve the action, after which all downstream code uses
canonical_source.
Actions whose class cannot be imported in any runner keep canonical_source = None. They
cannot be executed and any lookup by canonical will correctly return no match.
Consequences¶
- Single internal identifier. After runner initialization, every importable action has a
non-
Nonecanonical_source. Internal dispatch code (e.g.ProjectExecutor) can match bycanonical_sourcealone without any source-alias fallback. - No "absent means equal" inference. The WM no longer infers a relationship between alias and canonical from the absence of an entry in the ER response. The ER states the canonical explicitly for every action it can import.
- Multiple runners, first wins. When several runners initialize for the same project, the first one to successfully report a canonical for an action sets it. Subsequent runners skip already-resolved actions. All runners for the same project should agree (same class = same canonical), so ordering does not affect correctness.
- Import failures surface cleanly. An action absent from the ER's response is one the ER
could not import. Its
canonical_sourcestaysNone, and execution attempts on it will fail at the lookup stage rather than silently matching by alias.