Private
Public Access
archive: metadata nil sentinel and promotion
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user