Skip to content

feat(tags): add roar tag why provenance walk#231

Merged
christophergeyer merged 3 commits into
rc/0.4.0from
cg/tag-why
Jul 14, 2026
Merged

feat(tags): add roar tag why provenance walk#231
christophergeyer merged 3 commits into
rc/0.4.0from
cg/tag-why

Conversation

@christophergeyer

Copy link
Copy Markdown
Member

feat(tags): add roar tag why provenance walk

Implements the roar tag why verb from the audit design doc (20260519 roar audit.md), which was specified but not implemented — the one functional gap found in the roar tag QA on rc/0.4.0.

What it does

roar tag why <kind[=value]> <target> explains how an artifact acquired a tag, walking the inheritance path back to the human act that asserted it:

$ roar tag why contains_pii out_empty.txt
Why does out_empty.txt have contains_pii?
  out_empty.txt: contains_pii=present — inherited via job a79f9cc1
      └─ `roar tag bind` on done.marker authorized this across sessions
          └─ done.marker: contains_pii=present — inherited via job e40a10da
              └─ raw.csv: contains_pii=present — user `roar tag add` (born bound)

Each leaf is a human act (tag add / run --add-tag); a cross-session hop names the bind that authorized it — matching the doc's "walks the pointers … terminating at a tag add or a bind."

How

A read-only traversal over data that already exists — the per-value {value, origin, job} records, the tag.bind ledger, and job → inputs (get_by_uid + get_inputs). No schema change, no new storage, no migration. The value author even left the hook (tag_display_values docstring references "a future roar tag why").

Changes (roar only — local read, no glaas-api/site coupling)

  • application/query/requests.pyTagWhyRequest
  • application/query/results.pyTagWhySummary (renders the WhyNode forest)
  • application/tags.pyWhyNode + TagService.why() walk (cycle-guarded; graceful on missing producers)
  • application/query/tag.pytag_why() / build_tag_why_summary()
  • cli/commands/tag.pyroar tag why command + help
  • tests/unit/test_tag_why.py — 9 tests (user-leaf, one-hop system, cross-session bind annotation, --add-tag session-scoped leaf, value filter, absent tag, missing producer, cycle guard, non-artifact rejection)

Verification

  • pytest tests/unit/test_tag_why.py9 passed
  • pytest tests/unit/test_tag_* (regression) → 88 passed
  • ruff check / ruff format --check / mypy on changed modules → clean
  • Manual repro on a real cross-session/bind lineage → correct walks
  • Full suite: 2112 passed, 6 failed — all 6 pre-existing on rc/0.4.0 (register -y-confirmation #224 + telemetry tests, unrelated to this change; verified they fail identically on base). Heads-up: those stale tests want updating on rc/0.4.0 separately.

🤖 Generated with Claude Code

Explain how an artifact acquired a tag by walking the stored per-value
{value, origin, job} pointers and the bind ledger back to the human act
(tag add / run --add-tag), annotating any cross-session hop with the
bind that authorized it. Read-only over existing data — no schema change.

Adds TagWhyRequest, TagWhySummary/WhyNode, TagService.why, and the
`roar tag why <kind[=value]> <target>` CLI command, plus unit coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chrisgeyertreqs and others added 2 commits July 13, 2026 22:29
`roar tag why` only explains an artifact's tag, but `@N` resolves to a
job — a valid target for every other `roar tag` subcommand. The old
error said to "target a tracked artifact," implying the reference was
untracked, when in fact it named a perfectly tracked job. Distinguish
the job case and point at the job's output artifacts (or `tag show @N`).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@christophergeyer
christophergeyer merged commit ea8df68 into rc/0.4.0 Jul 14, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants