archive: metadata nil sentinel and promotion

This commit is contained in:
ed
2026-07-05 12:25:58 -04:00
parent a8781f06c4
commit 7a0eb0f66e
10 changed files with 0 additions and 0 deletions
@@ -0,0 +1,235 @@
# 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)
1. `AGENTS.md` (project root) — operating rules
2. `conductor/workflow.md` — the workflow
3. `conductor/edit_workflow.md` — the edit workflow
4. `conductor/code_styleguides/data_oriented_design.md` — the "Prefer Fewer Types" principle (the canonical rationale)
5. `conductor/code_styleguides/error_handling.md` — the `Result[T]` convention (Rule #0: read first)
6. `conductor/code_styleguides/type_aliases.md` — the 10 TypeAliases convention
7. `docs/reports/SSDL_CAMPAIGN_ABORTED_20260624.md` — the post-mortem explaining why this is a type-dispatch problem, NOT a nil-check problem
8. `src/type_aliases.py` (current 30 lines)
9. `scripts/code_path_audit/code_path_audit.py` (consumer detection)
10. `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)
```python
# 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)
```python
# 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:
```python
# 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:
```python
# 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:
1. **CommsLogEntry** (~150 sites): `session_logger.py`, `multi_agent_conductor.py`, `app_controller.py`
2. **HistoryMessage** (~80 sites): `ai_client.py` per-vendor history
3. **FileItem** (~200 sites): `aggregate.py`, `app_controller.py`, `gui_2.py`
4. **ToolDefinition + ToolCall** (~150 sites): `mcp_client.py`, `ai_client.py` tool loop section
5. **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)
```bash
# 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)
```bash
# 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 plan
- `conductor/tracks/metadata_promotion_20260624/metadata.json` — the metadata
- `conductor/tracks/metadata_promotion_20260624/state.toml` — the state
- `docs/reports/SSDL_CAMPAIGN_ABORTED_20260624.md` — the post-mortem explaining the type-dispatch root cause
- `conductor/tracks/any_type_componentization_20260621/plan.md` — the grandparent plan
- `src/type_aliases.py` — the current Metadata definition
- `scripts/code_path_audit/code_path_audit.py` — the consumer detection
- `scripts/code_path_audit/code_path_audit_ssdl.py` — the effective codepaths metric
- `conductor/code_styleguides/data_oriented_design.md` — the "Prefer Fewer Types" principle