Data Model: Canonical State Authority & Single Metadata Writer
Feature: 051-canonical-state-authority-single-metadata-writer Date: 2026-03-18
Entities
FeatureMetadata (meta.json)
The canonical metadata document for a feature. Owned exclusively by feature_metadata.py.
Required fields (always present, validated on every write):
| Field | Type | Description |
|---|---|---|
feature_number | str | Zero-padded feature number (e.g., "051") |
slug | str | Full feature slug (e.g., "051-canonical-state-authority-single-metadata-writer") |
feature_slug | str | Alias for slug (backward compatibility) |
friendly_name | str | Human-readable title |
mission | str | Mission key (e.g., "software-dev", "research", "documentation") |
target_branch | str | Branch to merge into (e.g., "2.x", "main") |
created_at | str | ISO 8601 timestamp |
Optional fields (present only when relevant operation has occurred):
| Field | Type | Set by | Description |
|---|---|---|---|
vcs | str | set_vcs_lock() | VCS type ("git") |
vcs_locked_at | str | set_vcs_lock() | ISO 8601 timestamp of VCS lock |
accepted_at | str | record_acceptance() | ISO 8601 timestamp of acceptance |
accepted_by | str | record_acceptance() | Actor who accepted |
acceptance_mode | str | record_acceptance() | How accepted (e.g., "standard", "orchestrator") |
accepted_from_commit | str | record_acceptance() | Git commit at acceptance time |
accept_commit | str | record_acceptance() | Git commit created by acceptance |
acceptance_history | list[dict] | record_acceptance() | Bounded list (max 20) of past acceptances |
merged_at | str | record_merge() | ISO 8601 timestamp of merge |
merged_by | str | record_merge() | Actor who merged |
merged_into | str | record_merge() | Target branch merged into |
merged_strategy | str | record_merge() | Merge strategy used |
merged_push | bool | record_merge() | Whether push was performed |
merged_commit | str | finalize_merge() | Final merge commit hash |
merge_history | list[dict] | record_merge() | Bounded list (max 20) of past merges |
documentation_state | dict | set_documentation_state() | Documentation mission state (see DocumentationState) |
source_description | str | Feature creation | Original feature description text |
Forward compatibility: Unknown fields are preserved during read-modify-write. The API does not strip unrecognized keys.
AcceptanceHistoryEntry
A single entry in the acceptance_history array.
| Field | Type | Description |
|---|---|---|
accepted_at | str | ISO 8601 timestamp |
accepted_by | str | Actor |
acceptance_mode | str | Mode (standard/orchestrator) |
accepted_from_commit | str | Source commit |
accept_commit | str | Created commit |
MergeHistoryEntry
A single entry in the merge_history array.
| Field | Type | Description |
|---|---|---|
merged_at | str | ISO 8601 timestamp |
merged_by | str | Actor |
merged_into | str | Target branch |
merged_strategy | str | Strategy |
merged_push | bool | Push performed |
merged_commit | `str \ | None` |
StatusSnapshot (status.json) — Existing, No Changes
Materialized view of all WP lane states, derived from status.events.jsonl. Already canonical. This sprint does not modify the status model — it only changes who reads it (acceptance now reads this instead of Activity Log).
Compatibility Views — Existing, Downgraded
These remain generated but are no longer read for workflow decisions:
| View | Location | Generated by |
|---|---|---|
WP frontmatter lane | tasks/WP*.md YAML frontmatter | legacy_bridge.update_frontmatter_views() |
| Activity Log | tasks/WP*.md body section | legacy_bridge.update_all_views() |
| Status block | tasks.md generated section | legacy_bridge.update_tasks_md_views() |
State Authority Hierarchy
CANONICAL (source of truth):
status.events.jsonl → Lane transitions (append-only)
status.json → Materialized snapshot (derived from events)
meta.json → Feature metadata (single writer: feature_metadata.py)
DERIVED (compatibility views, non-authoritative):
WP frontmatter lane → Generated after each transition
Activity Log body → Human narrative, generated after each transition
tasks.md status → Dashboard view, generated after each transition
Mutation API Surface
feature_metadata.py
├── load_meta(feature_dir) → dict[str, Any] | None
├── write_meta(feature_dir, meta) → None [atomic, validated]
├── record_acceptance(feature_dir, accepted_by, mode, from_commit, accept_commit) → dict
├── record_merge(feature_dir, merged_by, merged_into, strategy, push) → dict
├── finalize_merge(feature_dir, merged_commit) → dict
├── set_vcs_lock(feature_dir, vcs_type, locked_at) → dict
├── set_documentation_state(feature_dir, state) → dict
├── set_target_branch(feature_dir, branch) → dict
├── validate_meta(meta) → list[str] [returns error messages, empty = valid]
└── _atomic_write(path, content) → None [internal]
Each mutation helper: 1. Calls load_meta() (read current) 2. Applies the specific mutation 3. Calls validate_meta() (check required fields) 4. Calls write_meta() (atomic write with standard formatting) 5. Returns the updated dict