8.6 KiB
Tier 2 Startup Brief: metadata_promotion_20260624
Context
This is the actual fix for the 4.01e22 combinatoric explosion. Promotes Metadata: TypeAlias = dict[str, Any] to a typed @dataclass(frozen=True, slots=True) and migrates all 695 consumer functions + 213 access sites to direct field access.
Recommendation: Run in parallel with code_path_audit_phase_3_provider_state_20260624 (the 27-call-site provider_state migration). The two tracks are orthogonal — phase 3 touches provider_state infrastructure, this track touches Metadata consumers. No merge conflicts expected.
The code_path_audit_phase_3_provider_state_20260624 track is listed as blocked_by in metadata.json but the blocking is recommended, not strict. If the user wants this track to start first, update metadata.json accordingly.
MANDATORY Pre-Action Reading (per agent protocol)
AGENTS.md(project root) — operating rulesconductor/workflow.md— the workflowconductor/edit_workflow.md— the edit workflowconductor/code_styleguides/data_oriented_design.md— the "Prefer Fewer Types" principle (the canonical rationale)conductor/code_styleguides/error_handling.md— theResult[T]convention (Rule #0: read first)conductor/code_styleguides/type_aliases.md— the 10 TypeAliases conventiondocs/reports/SSDL_CAMPAIGN_ABORTED_20260624.md— the post-mortem explaining why this is a type-dispatch problem, NOT a nil-check problemsrc/type_aliases.py(current 30 lines)scripts/code_path_audit/code_path_audit.py(consumer detection)scripts/code_path_audit/code_path_audit_ssdl.py(effective codepaths metric)
First commit of this track must include TIER-2 READ <list> before metadata_promotion_20260624 in the message.
The Metadata dataclass (Phase 0)
# src/type_aliases.py: REPLACE line 5
# BEFORE:
Metadata: TypeAlias = dict[str, Any]
# AFTER:
@dataclass(frozen=True, slots=True)
class Metadata:
role: str = ""
content: Any = None
tool_calls: Any = None
tool_call_id: str = ""
name: str = ""
args: Any = None
source_tier: str = "main"
model: str = "unknown"
id: str = ""
ts: str = ""
description: str = ""
depends_on: tuple[str, ...] = ()
status: str = ""
manual_block: bool = False
completed_tickets: int = 0
auto_start: bool = False
command: str = ""
script: str = ""
output: Any = None
error: str = ""
tier: str = ""
path: str = ""
full_path: str = ""
filename: str = ""
mtime: float = 0.0
size: int = 0
# ... ~150-180 distinct keys from the .get + [] site analysis ...
def to_dict(self) -> dict[str, Any]:
return {k: v for k, v in asdict(self).items() if v is not None or k in _NON_NULL_KEYS}
@classmethod
def from_dict(cls, raw: dict[str, Any]) -> 'Metadata':
valid_fields = {f.name for f in fields(cls)}
return cls(**{k: v for k, v in raw.items() if k in valid_fields})
The exact list of fields is determined by the union of distinct keys used across all 213 access sites. The spec §FR1 has the seed list; the worker should expand it based on git grep -hoE output during Phase 0.
Migration pattern (per consumer site)
# BEFORE:
x = entry.get('model', 'unknown')
y = entry.get('input_tokens', 0) or 0
z = entry.get('source_tier', 'main')
if entry.get('manual_block', False):
...
role = entry['role']
if 'depends_on' in entry:
deps = entry['depends_on']
# AFTER (with Metadata dataclass):
x = entry.model or 'unknown'
y = entry.input_tokens or 0
z = entry.source_tier or 'main'
if entry.manual_block:
...
role = entry.role
if entry.depends_on:
deps = entry.depends_on
For polymorphic construction:
# BEFORE:
entry = {'role': 'user', 'content': 'hi'}
# AFTER:
entry = Metadata(role='user', content='hi')
# Or for dynamic dicts:
entry = Metadata.from_dict(raw_dict)
For JSON serialization:
# BEFORE:
json.dumps(entry)
# AFTER:
json.dumps(entry.to_dict())
Phased migration order
The 695 consumers distribute across 5 sub-aggregates. Migrate sub-aggregate by sub-aggregate:
- CommsLogEntry (~150 sites):
session_logger.py,multi_agent_conductor.py,app_controller.py - HistoryMessage (~80 sites):
ai_client.pyper-vendor history - FileItem (~200 sites):
aggregate.py,app_controller.py,gui_2.py - ToolDefinition + ToolCall (~150 sites):
mcp_client.py,ai_client.pytool loop section - Metadata direct usage (~115 sites): the catch-all (gui_2.py general, models.py, paths.py, etc.)
Effective codepaths metric
Expected progression:
| Phase | Effective codepaths | Consumers |
|---|---|---|
| Baseline (master) | 4.014e+22 | 695 |
| After Phase 1 (CommsLogEntry) | ~4e+19 | ~545 (150 migrated away) |
| After Phase 2 (HistoryMessage) | ~3e+19 | ~465 |
| After Phase 3 (FileItem) | ~2e+18 | ~265 |
| After Phase 4 (ToolDefinition+ToolCall) | ~1e+17 | ~115 |
| After Phase 5 (Metadata direct) | ~5e+15 | ~0 |
These are estimates based on the assumption that each migration removes ~2 branches per consumer. The actual drops depend on the specific code. Re-measure after each phase.
Pre-flight verification (before Phase 0)
# Verify the current state
uv run python -c "
import sys
sys.path.insert(0, 'scripts/code_path_audit')
sys.path.insert(0, 'src')
from code_path_audit import build_pcg
from code_path_audit_ssdl import count_branches_in_function
pcg = build_pcg('src').data
metadata_consumers = pcg.consumers.get('Metadata', [])
total = sum(2 ** count_branches_in_function(f, 'src') for f in metadata_consumers)
print(f'Baseline: {total:.3e} ({len(metadata_consumers)} consumers)')
"
# Expect: 4.014e+22 (695 consumers)
# Verify the 213 access sites
git grep -E "\.get\('[a-z_]+'," HEAD -- 'src/*.py' | wc -l
# Expect: 107
git grep -E "\[[ ]*'[a-z_]+'[ ]*\]" HEAD -- 'src/*.py' | wc -l
# Expect: 106
# Verify the 5 sub-aggregate TypeAliases all point to Metadata
git show HEAD:src/type_aliases.py | grep "TypeAlias"
# Expect:
# CommsLogEntry: TypeAlias = Metadata
# HistoryMessage: TypeAlias = Metadata
# FileItem: TypeAlias = Metadata
# ToolDefinition: TypeAlias = Metadata
# ToolCall: TypeAlias = Metadata
# Verify all 7 audit gates pass
uv run python scripts/audit_weak_types.py --strict
uv run python scripts/generate_type_registry.py --check
uv run python scripts/audit_main_thread_imports.py
uv run python scripts/audit_no_models_config_io.py
uv run python scripts/audit_code_path_audit_coverage.py --input-dir docs/reports/code_path_audit/latest --strict
uv run python scripts/audit_exception_handling.py --strict
uv run python scripts/audit_optional_in_3_files.py --strict
# All exit 0
Post-track verification (after Phase 6)
# VC1: Metadata is @dataclass
git show HEAD:src/type_aliases.py | head -20
# Expect: @dataclass(frozen=True, slots=True) class Metadata:
# VC2: 0 .get sites on Metadata consumers
git grep -E "\.get\('[a-z_]+'," HEAD -- 'src/*.py' | wc -l
# Expect: <20 (only legitimate non-Metadata uses)
# VC3: 0 subscript sites on Metadata consumers
git grep -E "\[[ ]*'[a-z_]+'[ ]*\]" HEAD -- 'src/*.py' | wc -l
# Expect: <20
# VC4: 12+ tests pass
uv run python -m pytest tests/test_metadata_dataclass.py -v
# VC5: 5 sub-aggregate TypeAliases all point to Metadata
git show HEAD:src/type_aliases.py | grep "TypeAlias = Metadata"
# VC6: Effective codepaths drops by >= 2 orders of magnitude
uv run python -c "
import sys
sys.path.insert(0, 'scripts/code_path_audit')
sys.path.insert(0, 'src')
from code_path_audit import build_pcg
from code_path_audit_ssdl import count_branches_in_function
pcg = build_pcg('src').data
metadata_consumers = pcg.consumers.get('Metadata', [])
total = sum(2 ** count_branches_in_function(f, 'src') for f in metadata_consumers)
print(f'Post-track: {total:.3e} (baseline: 4.014e+22)')
"
# Expect: < 1e+20
See also
conductor/tracks/metadata_promotion_20260624/spec.md— the full spec (10 VCs)conductor/tracks/metadata_promotion_20260624/plan.md— the 5-phase planconductor/tracks/metadata_promotion_20260624/metadata.json— the metadataconductor/tracks/metadata_promotion_20260624/state.toml— the statedocs/reports/SSDL_CAMPAIGN_ABORTED_20260624.md— the post-mortem explaining the type-dispatch root causeconductor/tracks/any_type_componentization_20260621/plan.md— the grandparent plansrc/type_aliases.py— the current Metadata definitionscripts/code_path_audit/code_path_audit.py— the consumer detectionscripts/code_path_audit/code_path_audit_ssdl.py— the effective codepaths metricconductor/code_styleguides/data_oriented_design.md— the "Prefer Fewer Types" principle