gabriel / muse public
MUSE-215-CREATED-BY-FALLBACK-FREEZE.md markdown
180 lines 7.5 KB
Raw
sha256:5eff0848db1d0748f3a195ca871a9275e7455d447690411ec2d83434e11baa65 feat(#216): persist write-once BranchMeta creation provenance Human minor ⚠ breaking 3 days ago

FREEZE — musehub#215 muse branch --json created_by author fallback (Thinking)

id: muse-215-created-by-fallback
model: Thinking
frozen: true
status: freeze-review-pass
date: 2026-09-17
author: aaronrene
repo: gabriel/muse
ticket: https://staging.musehub.ai/gabriel/musehub/issues/215
consumes:
  - gabriel comment sha256:a4b55431b9694cec98d3d0b900214b68cdeb75e13c3be37dd7c14f27129e59dd
escalation: none
tier3_gates:
  - merge to gabriel/muse main (Gabriel review)
blocked_by: freeze review must be pass before {step}b Auto
out_of_scope:
  - musehub#216 BranchMeta persistence
  - push/clone protocol
  - changing created_by JSON type from str | None to an object

1. WHAT is frozen

Statement of truth {step}b Auto consumes without re-deriving:

muse branch --json's created_by field is tip-commit authorship, not branch-creation provenance. Today it reads only CommitRecord.agent_id (muse/cli/commands/branch.py:872 on this tree; :854 on installed 0.2.0rc15):

created_by: str | None = (rec.agent_id if rec and rec.agent_id else None)

CommitRecord (muse/core/commits.py) has two authorship fields:

Field Default Meaning
author: str "" human handle (e.g. "aaronrene")
agent_id: str "" agent identity (e.g. "claude-code")

When the tip is human-authored, agent_id is "" and created_by is unconditionally null even though .author is populated.

Reproduced 2026-09-17 in a throwaway repo (installed muse 0.2.0rc15):

Tip author agent_id created_by today
muse commit -m "human authored tip" aaronrene "" null

Frozen resolution rule

For each listed branch, after rec = read_commit(...) (or None if no tip):

created_by = _first_nonempty(rec.agent_id, rec.author) if rec else None

where _first_nonempty(*values) returns the first value whose str.strip() is non-empty, then passed through sanitize_provenance (muse/core/validation.py:402), else None.

Equivalence with Gabriel's suggested rec.agent_id or rec.author or None for the empty-string case. Stricter than raw or on whitespace-only strings: " " is absent, not a handle.

Precedence: agent_id wins when both are non-empty after strip. That matches the ticket.

JSON contract (unchanged type)

_BranchEntryJson.created_by stays str | None (branch.py:104). Do not change it to the shelf object {"handle", "kind"}. That shape is #216.

Human (non---json) muse branch output is unchanged. This ticket is the JSON field only.

2. HOW is frozen

Single edit site: the assignment at muse/cli/commands/branch.py:872 (and any tiny helper extracted next to it in the same module if tests need a unit seam).

Allowed:

  • Extract _created_by_from_record(rec: CommitRecord | None) -> str | None in branch.py.
  • Use sanitize_provenance on the chosen handle (identity field; strips tab/newline/C0/C1).
  • Add created_by to _LIST_REQUIRED_KEYS in tests/test_branch_json_schema.py (currently omits created_by / intent / resumable even though the TypedDict already has them).
  • New tests in tests/test_cmd_branch.py and/or tests/test_branch_json_schema.py.

Forbidden:

  • Writing created_by / created_at into BranchMeta / .muse/config.toml.
  • Changing muse push, fetch, clone, or MuseHub wire types.
  • Changing committed_at in the JSON (it remains the tip timestamp).
  • Changing shelf's created_by object shape or calling _resolve_created_by from shelf.
  • Touching task/video-timeline-vid1 or any file outside branch.py + the test files named above + this freeze + living docs.

3. Fail-closed rules

# Condition Result
F1 rec is None (empty / never-committed branch) created_by is JSON null
F2 agent_id strip-empty, author strip-empty null
F3 agent_id strip-empty, author non-empty sanitized author
F4 agent_id non-empty (author anything) sanitized agent_id
F5 Control characters in the chosen handle stripped by sanitize_provenance; if nothing remains, null
F6 Exception reading the tip keep existing branch --json error behavior; do not invent a handle

No new network calls. No new config keys.

4. Security

  • Display-only. Source is the local CommitRecord already on disk.
  • Use sanitize_provenance, not raw interpolation, so ESC/CSI cannot ride created_by into a consuming TTY or log (validation.py:402–410).
  • Do not log full commit records in test failure messages beyond the two authorship fields.
  • No secrets, no auth change, no wire change. Escalation: none.

5. Seven-tier test matrix

Tier Proves How
1 unit F1–F5 on _created_by_from_record (or the assignment) with constructed CommitRecords: None; both empty; author only; agent only; both set (agent wins); whitespace-only author; agent with ESC byte tests/test_cmd_branch.py
2 integration muse branch --json via CliRunner on a fixture repo after a human commit (no --agent-id) returns a non-null created_by equal to user.handle / commit author extend test_branch_json_schema.py
3 e2e Throwaway repo: human commit → created_by == author; second commit with --agent-idcreated_by == agent_id on the same branch CLI e2e in the same test module
4 stress 200 branches listed via --json completes; every entry has the created_by key cap runtime, no OOM
5 data-integrity Run --json twice on an unchanged repo → byte-identical created_by values; after a new human commit on a previously agent tip, created_by does change (tip-derived; this ticket does not freeze creation-time) assert both
6 performance 200-branch list < 5s on the test fixture wall clock bound
7 security ESC in author does not appear in JSON; no MUSE_* / key material in output sanitize_provenance assertion

Existing TestListSchemaI must keep passing. Adding created_by to _LIST_REQUIRED_KEYS is required so a future regression cannot drop the key.

6. Definition of Done (for {step}b, not this freeze)

  • All seven tiers green locally.
  • /build-verification-reviewpass.
  • #216 not started.
  • Living docs updated together.
  • Feature-branch commit on feat/215-branch-json-created-by-fallback.

7. Review discipline (file+line citations)

ok review --freeze docs/MUSE-215-CREATED-BY-FALLBACK-FREEZE.md must return pass before any edit to branch.py beyond a helper that is specified here.

Claim file+line
created_by reads only agent_id muse/cli/commands/branch.py:872
JSON type str \| None muse/cli/commands/branch.py:104
CommitRecord.author / agent_id muse/core/commits.py:210 / :215
sanitize_provenance muse/core/validation.py:402
schema test omits created_by tests/test_branch_json_schema.py:39-41
BranchMeta has no creation fields muse/cli/config.py:121-139
review_stamp:
  gate: mechanical
  reviewed_at: '2026-09-17T10:43:21Z'
  mechanical_verdict: pass
  produced_by: checklist_engine
  provider_kind: rule_engine
  reviewer_mode: agent
  reviewer_model: null
  reviewer_provider: local
  checklist_ids:
  - C1
  - C2
  - C3
  - C4
  - C5
  - C6
  - C7
  - C8
  checklist_source: builtin
  findings_count: 0
  override_applied: false
  kit_version: 0.1.0
  artifact_digest: sha256:009d150f71f7b921cb6089ebf6b800bc3f3e7905b042d682998fc2af763f2093
File History 1 commit
sha256:5eff0848db1d0748f3a195ca871a9275e7455d447690411ec2d83434e11baa65 feat(#216): persist write-once BranchMeta creation provenance Human minor 3 days ago