docs: RMGR-WP-0009 coding-assistant commit provenance trailers
Commits made by coding assistants are currently indistinguishable from hand-typed ones. state-hub has two commit identities, tegwick and custodian-sync; only Claude Code is identifiable, and only via a Co-Authored-By trailer added by a prompt instruction. Codex and Grok add nothing. Establishing on 2026-08-21 that a 14-hour Codex session had not collided with a concurrent Claude session required correlating process tables against commit timestamps, and could only conclude 'not me and not the sync bot'. Adds git trailers carrying assistant, model, and process, applied by a prepare-commit-msg hook wired through core.hooksPath. Author stays tegwick deliberately -- assistants run under Bernd's account because he supervises, and the trailers add provenance rather than a second actor. Scopes explicitly to the coding-assistant category and leaves room for kaizen agents (own agent ids and accounts) and scheduled OpenRouter runs as separate future mechanisms. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
41f06626c0
commit
e8eaaf9a7f
1 changed files with 240 additions and 0 deletions
240
workplans/RMGR-WP-0009-coding-assistant-commit-provenance.md
Normal file
240
workplans/RMGR-WP-0009-coding-assistant-commit-provenance.md
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
---
|
||||
id: RMGR-WP-0009
|
||||
type: workplan
|
||||
title: "Coding-assistant commit provenance trailers"
|
||||
domain: infotech
|
||||
repo: repo-manager
|
||||
status: proposed
|
||||
owner: codex
|
||||
topic_slug: infotech
|
||||
created: "2026-08-21"
|
||||
updated: "2026-08-21"
|
||||
parent_project: prj-state-hub-retirement
|
||||
related:
|
||||
- RMGR-WP-0004
|
||||
- RMGR-WP-0005
|
||||
- ADR-007
|
||||
- ADR-010
|
||||
---
|
||||
|
||||
# Coding-assistant commit provenance trailers
|
||||
|
||||
## Goal
|
||||
|
||||
Make commits written by coding assistants self-identifying — which assistant,
|
||||
which model, which process — so that "who changed this and did two sessions
|
||||
collide?" is answerable from `git log` alone.
|
||||
|
||||
## The gap
|
||||
|
||||
Observed 2026-08-21 while auditing `state-hub`. The repo has exactly two commit
|
||||
identities:
|
||||
|
||||
```text
|
||||
tegwick <bernd.worsch@gmail.com> 120 of last 200
|
||||
custodian-sync <custodian-sync@railiance.local> 80 of last 200
|
||||
```
|
||||
|
||||
Claude Code commits are identifiable only because they carry a
|
||||
`Co-Authored-By: Claude Opus 5` trailer, added by a prompt instruction in
|
||||
`~/.claude/CLAUDE.md`. Codex and Grok add nothing. So a commit made by Codex is
|
||||
byte-identical in provenance to one typed by hand, and the question "which
|
||||
assistant made this change" has no answer.
|
||||
|
||||
This is not hypothetical. On 2026-08-21 a Codex session ran for ~14 hours on the
|
||||
workstation while a Claude session worked the same repo. Establishing that they
|
||||
had *not* collided required correlating process tables against commit timestamps
|
||||
and a trailer only one of them writes — and the conclusion could only be stated
|
||||
as "not me and not the sync bot", never as "this was Codex".
|
||||
|
||||
It also weakens the multi-writer work already underway. `ADR-007` decision 2 and
|
||||
`ADR-010` are about several writers over one artifact set; per-writer provenance
|
||||
is the evidence those decisions need and currently lack.
|
||||
|
||||
## Category boundary — read before implementing
|
||||
|
||||
Three distinct kinds of non-human commit exist or are coming. **Only the first
|
||||
is in scope here.** The scheme must not foreclose the others.
|
||||
|
||||
| Category | Identity model | Status |
|
||||
| --- | --- | --- |
|
||||
| **Coding assistant** | Runs under Bernd's account; author stays `tegwick`. Interactive, returns to the operator for instruction. | **This workplan** |
|
||||
| **Kaizen agent** | Autonomous; will have its own agent id and account, and should commit as itself. | Future, not here |
|
||||
| **Scheduled / OpenRouter capacity** | Unattended runs against purchased inference capacity. | Separate category, not here |
|
||||
|
||||
**The author is deliberately not changed.** Coding assistants commit as Bernd
|
||||
because he is supervising, and that placement of responsibility is the point —
|
||||
it is not an accident to be corrected. These trailers add *provenance*, not a
|
||||
second actor. A future kaizen agent committing under its own identity is a
|
||||
different mechanism and a different decision.
|
||||
|
||||
## Define the trailer contract
|
||||
|
||||
```task
|
||||
id: RMGR-WP-0009-T01
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
Publish the trailer set as a repository standard in
|
||||
`docs/repository-standards_v0.1.md` — repo-manager owns repository standards
|
||||
under decision `747011c6`.
|
||||
|
||||
Requested core fields are **model and process**. Proposed shape, using git
|
||||
trailers so `git interpret-trailers` and `git log --format=%(trailers)` parse
|
||||
them without custom tooling:
|
||||
|
||||
```text
|
||||
Assistant: claude-code
|
||||
Assistant-Model: claude-opus-5
|
||||
Assistant-Process: 208772@bnt-lap001
|
||||
Assistant-Session: 354884ba-6e26-4918-8bcc-4fc675e419ee
|
||||
```
|
||||
|
||||
Two fields beyond the request, both cheap and both load-bearing for the stated
|
||||
purpose — **cut them if they are not wanted**, the scheme works without:
|
||||
|
||||
- **`@<host>` on the process.** A bare PID is ambiguous across the fleet;
|
||||
`bnt-lap001`, `railiance01`, and `coulombcore` all allocate from the same small
|
||||
number space. Without the host, two unrelated sessions can read as one.
|
||||
- **`Assistant-Session`.** PIDs are recycled by the OS and mean nothing once the
|
||||
process exits, so a PID alone cannot group a session's commits after the fact
|
||||
— which is exactly the collision question. Claude Code already exports
|
||||
`CLAUDE_CODE_SESSION_ID`; if Codex or Grok expose no equivalent, omit the field
|
||||
for that tool rather than inventing one.
|
||||
|
||||
Decide also whether `Co-Authored-By: Claude Opus 5` is kept, folded into
|
||||
`Assistant-Model`, or dropped. Keeping both duplicates the model.
|
||||
|
||||
## Implement the hook
|
||||
|
||||
```task
|
||||
id: RMGR-WP-0009-T02
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
A `prepare-commit-msg` hook that appends the trailers from environment, wired
|
||||
fleet-wide via `core.hooksPath` so it applies without per-repo installation.
|
||||
|
||||
There are no git hooks anywhere today — `core.hooksPath` is unset globally and
|
||||
locally, and no repo has a non-sample hook — so this is greenfield and cannot
|
||||
conflict with existing tooling.
|
||||
|
||||
Requirements:
|
||||
|
||||
- **Never block a commit.** Missing or unreadable environment means write no
|
||||
trailer, not fail. A provenance mechanism that stops work will be disabled.
|
||||
- **Idempotent.** Appending must not double a trailer already present, so
|
||||
`--amend` and rebases stay clean.
|
||||
- **Silent for humans.** A commit typed by Bernd with no assistant environment
|
||||
set gets no trailers and looks exactly as it does today.
|
||||
- Detection is from environment, not process-tree walking — the hook runs
|
||||
several levels below the agent and the tree is not reliable.
|
||||
|
||||
Prefer `core.hooksPath` over per-repo `.git/hooks` (not committable, lost on
|
||||
clone) and over committed hook files (needs opt-in per repo).
|
||||
|
||||
## Wire Claude Code
|
||||
|
||||
```task
|
||||
id: RMGR-WP-0009-T03
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
Environment is already sufficient, verified 2026-08-21:
|
||||
|
||||
```text
|
||||
CLAUDECODE=1
|
||||
CLAUDE_CODE_SESSION_ID=<uuid>
|
||||
CLAUDE_PID=<pid>
|
||||
CLAUDE_CODE_ENTRYPOINT=cli
|
||||
```
|
||||
|
||||
Only the **model** is missing — `CLAUDE_EFFORT` is exported but the model id is
|
||||
not. Resolve from `~/.claude/settings.json` `model`, or have the hook accept an
|
||||
`ASSISTANT_MODEL` override the session sets. Note that `settings.json` records
|
||||
the configured model, which is not always the model that served a given turn.
|
||||
|
||||
Once live, retire the `Co-Authored-By` prompt instruction in `~/.claude/CLAUDE.md`
|
||||
per T01's ruling — a hook cannot be forgotten mid-session the way a prompt
|
||||
instruction can, which is the main reason to prefer it.
|
||||
|
||||
## Wire Codex
|
||||
|
||||
```task
|
||||
id: RMGR-WP-0009-T04
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
Discover what the Codex CLI exports — no `CODEX_*` variables were present in the
|
||||
Claude session's environment on 2026-08-21, so this needs checking from inside a
|
||||
Codex session rather than assumed.
|
||||
|
||||
Codex is the case that motivates the workplan: it is the assistant currently
|
||||
invisible in `git log`, and the one that ran 14 hours alongside a Claude session.
|
||||
|
||||
If it exports nothing usable, set the variables in its launch wrapper. A stable
|
||||
per-session value is worth more than the exact PID if only one is available.
|
||||
|
||||
## Wire Grok
|
||||
|
||||
```task
|
||||
id: RMGR-WP-0009-T05
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Same discovery for Grok. Lowest priority of the three only because it appears
|
||||
least in current history — confirm that is still true before deciding.
|
||||
|
||||
## Report on assistant activity and collisions
|
||||
|
||||
```task
|
||||
id: RMGR-WP-0009-T06
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
The trailers are the input; this is the payoff. An `rmgr` report over
|
||||
`git log --format=%(trailers)` answering:
|
||||
|
||||
- which assistant and model touched a repo, over a window;
|
||||
- **overlapping sessions** — two `Assistant-Session` values with interleaved
|
||||
commits in one repo, which is the collision signal asked for;
|
||||
- commits with no trailer, split into human (expected) and assistant-not-yet-wired
|
||||
(a gap to close).
|
||||
|
||||
Keep it read-only over git. Do not add a hub table: this is derivable from the
|
||||
repositories, and `state-hub` is being retired under `STATE-WP-0079`.
|
||||
|
||||
## Handle existing history
|
||||
|
||||
```task
|
||||
id: RMGR-WP-0009-T07
|
||||
status: todo
|
||||
priority: low
|
||||
```
|
||||
|
||||
History is not rewritten — the same reasoning as `ADR-007` § Migration option 2.
|
||||
Commits before the cutover stay unattributed, and the report must present that
|
||||
honestly as "unknown", never infer an assistant from timing or message style.
|
||||
|
||||
Record the cutover commit so the report can distinguish *"before provenance
|
||||
existed"* from *"assistant not wired"*. Without that boundary every pre-cutover
|
||||
commit reads as a gap.
|
||||
|
||||
Also decide the `custodian-sync` identity's treatment: it is automation with its
|
||||
own committer identity already, so it likely needs no trailer — but it should be
|
||||
named in the standard so it is not later mistaken for an unwired assistant.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] Trailer contract published in `docs/repository-standards_v0.1.md`
|
||||
- [ ] Hook live via `core.hooksPath`; never blocks, idempotent, silent for humans
|
||||
- [ ] Claude, Codex, and Grok each emit trailers, or are recorded as unable with a reason
|
||||
- [ ] `git log` alone answers "which assistant, which model, which session"
|
||||
- [ ] Overlapping-session report runs against at least one real collision window
|
||||
- [ ] Cutover commit recorded; pre-cutover history reported as unknown, not inferred
|
||||
Loading…
Add table
Add a link
Reference in a new issue