Skip to content

linker: 11+ DSL constructs silently dropped during AppSpec construction (channels, messages, subscriptions, templates, archetypes, ...) — systemic #1075

@manwithacat

Description

@manwithacat

Symptom

This is the same class of bug as #1070 (RESOLVED v0.67.147 for domain_services) but generalised — a systematic audit of build_appspec against AppSpec's field set found 16 fields that exist on both ModuleFragment AND AppSpec but are NOT mapped from merged_fragment in build_appspec's final AppSpec(...) construction. Of these 16, 11 are confirmed-dropping in real DSL authored by example apps + fixtures:

Field Apps/fixtures with parsed content silently dropped
archetypes support_tickets (2), pra (3)
assets pra (4)
channels simple_task (3), pra (11)
documents pra (4)
event_model simple_task (1), pra (1)
hless_pragma pra (1)
messages simple_task (5), pra (7)
projections pra (7)
streams pra (9)
subscriptions simple_task (2), pra (7)
templates pra (5)

The remaining 5 fields (data_products, e2e_flows, fixtures, interfaces, policies) are unmapped in build_appspec too — they just aren't exercised by current example apps / fixtures, so we can't observe the drop. Same bug shape; same fix shape.

Reproduction

from pathlib import Path
from dazzle.core.parser import parse_modules
from dazzle.core.linker import build_appspec

base = Path("examples/simple_task")
modules = parse_modules(sorted((base / "dsl").glob("*.dsl")))
appspec = build_appspec(modules, "simple_task.core")

# Per-module: simple_task.messaging has 3 channels, 5 messages, 2 subscriptions
# simple_task.events has 1 event_model
# Merged appspec: ALL zero / None
print(f"channels: {len(appspec.channels)} (parsed: 3)")        # 0
print(f"messages: {len(appspec.messages)} (parsed: 5)")        # 0
print(f"subscriptions: {len(appspec.subscriptions)} (parsed: 2)")  # 0
print(f"event_model: {appspec.event_model}")  # None (parsed: 1)

Same shape on pra — 7 constructs drop with non-trivial counts.

Root cause (same as #1070, 11× over)

build_appspec in src/dazzle/core/linker.py:194 constructs the final AppSpec(...) by explicitly mapping each field from merged_fragment. The explicit mapping list is missing 16 entries. The fields exist on ModuleFragment (so the parser writes them); they exist on AppSpec (so consumers can read them); but the bridge is incomplete.

Possible secondary cause for some: merge_fragments in linker_impl.py:1444 also doesn't always thread these through (some, like domain_services before v0.67.147, are missing from the SymbolTable + merge return). The full pipeline has 3 stages:

  1. Parsedsl_parser_impl/*.py writes to module.fragment.X
  2. Symbol-collectlinker_impl.build_symbol_table collects from per-module fragments
  3. Mergelinker_impl.merge_fragments builds unified ModuleFragment
  4. AppSpec constructionlinker.build_appspec maps merged fragment → final AppSpec

A drop can happen at stage 2 (no symbol collection), stage 3 (no merge mapping), or stage 4 (no AppSpec mapping). The fix for each field is to verify the field is preserved at all stages — the same 5-touch-point check from the v0.67.147 Agent Guidance.

Consumer impact

Each dropped field has consumers that operate on the dropped data and produce wrong answers:

  • appspec.channels is read by event-bus and notification routing
  • appspec.messages is read by the channel-routing dispatch
  • appspec.subscriptions is read by the projection-event wiring
  • appspec.event_model is read by HLESS validation
  • appspec.templates is read by the runtime renderer for template: DSL declarations
  • ... etc

Every consumer is operating on [] / None regardless of DSL content, exactly like the domain_services validator's "step will fail at runtime" warning that didn't reflect the actual DSL.

Effectively, every Dazzle app silently has 11+ feature areas disabled at the runtime layer, masked by the fact that the parser reports them correctly so authors think they're wired up.

Suggested fix

Same shape as the v0.67.147 fix, replicated 11×:

  1. Audit SymbolTable for missing field declarations
  2. Audit build_symbol_table for missing per-module collection loops
  3. Audit merge_fragments for missing return kwargs
  4. Audit build_appspec's AppSpec(...) mapping for missing keyword args
  5. Add a regression test per field — extends test_domain_services_propagated_to_appspec from cycle 121 into a parametrised version that asserts every AppSpec field with a corresponding ModuleFragment field round-trips through build_appspec

Better: write the parametrised regression test FIRST, see all 11+ failures, then fix one field at a time.

Discovered by

/improve cycle 128 framework-ux lane, proactive audit against the v0.67.147 Agent Guidance note: "This pattern likely also affects params or other less-tested constructs — worth a separate audit cycle." The audit grepped build_appspec's explicit field mapping against AppSpec.model_fields and surfaced 16 candidate drops; cross-app verification confirmed 11 with measurable impact.

Companion to #1070 (which fixed domain_services). This issue covers the remaining 16 field-propagation gaps that the cycle 121 fix didn't touch.

The systemic nature is its own finding: a single linker change that misses one field is easy to miss; 16 fields silently missing across 4 stages suggests the linker pipeline needs a generic propagation pattern, not 16 individual fixes. Worth discussing the design before implementing the fixes one-at-a-time.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions