Skip to content

feat(delivery): record the branch and PR on the spec, and keep attribution out of the record - #29

Merged
protonspy merged 1 commit into
mainfrom
feat/delivery-record
Aug 27, 2026
Merged

feat(delivery): record the branch and PR on the spec, and keep attribution out of the record#29
protonspy merged 1 commit into
mainfrom
feat/delivery-record

Conversation

@protonspy

Copy link
Copy Markdown
Owner

Two halves of one question: what the record of a delivery says.

The spec records where it is being built

A branch was the one part of this methodology that left no trace in the artifacts. The spec said what the feature does and which boxes were ticked; git said a branch called feat/user-auth had been sitting unmerged for three weeks. Nothing joined the two, so which of these actually shipped was answerable only by somebody holding both halves — and under autonomy: auto there is nobody holding either.

Three keys on requirements.md, beside the kickoff answers, because that file is already the spec's header:

branch: feat/user-auth
pr: 28
delivery: in-progress | in-review | merged | abandoned

The vocabulary is closed for the same reason a task's flags are: an open one produces three spellings of done inside a month, and "what is still unfinished" is exactly the question a synonym destroys. Absent is the fifth state and needs no name — it is what every spec written before this looks like, which is why the validator grades these only when present. delivery: rather than status:, which already means the approval seal on a plan.

scc spec track user-auth --here      # the branch from the checkout → in-progress
scc spec track user-auth --pr 28     # → in-review, without restating the branch
scc spec sync                        # ask git and gh, write the answer back
scc spec list                        # the record, beside the phases

track records what the caller knows; sync derives what git knows. sync walks every spec, asks git and — when installed — gh, and writes back under the same verify-and-roll-back contract as scc patch. Neither guesses: a deleted branch with no PR to ask about is reported undetermined and left exactly as it was, because merged and abandoned are indistinguishable once the ref is gone.

Two things were wrong in the first cut and the failures were instructive:

  • Merged is not "is an ancestor of the base". A branch created ten seconds ago and never committed to satisfies that trivially, so the first smoke test declared a spec delivered before a line of it existed. Merged is ahead == 0 and behind > 0. The one case left ambiguous is a fast-forward merge nothing has advanced past, where no ref can tell "just branched" from "just landed" — resolved as not merged, because this record exists to surface unfinished work and the error that leaves a loose end visible is the one to make.
  • A settled record is not re-litigated. A deleted branch on a spec already merged is what a merged branch is supposed to look like; warning about it would put a line on every finished spec forever, which is how a report stops being read.

New findings: spec.delivery-invalid, spec.pr-invalid, spec.branch-invalid, and spec.delivery-unstated — a branch or PR recorded with no state is half a record, which is the loose end this exists to surface.

internal/git is the fourth integration package, alongside rtk, headroom and codegraph. git and gh live together because the question has two halves and no caller should have to route between them. Nothing in it installs and nothing writes — every call is a query, which is what makes running it across a whole workspace safe by construction.

Plans are deliberately out for now, for a naming reason rather than a principle: pr: on a plan already means the delivery shape plan-run asks for at kickoff (per-plan/per-group), so one key would carry two meanings on one file. Plan tracking starts by renaming that answer.

The record carries no attribution

The work is the user's, and the record says so. No Co-Authored-By for an assistant, no session link, no "generated with" footer, no naming of a model, vendor, or harness — not in a commit message, not in a PR title or body, not in a branch name. Say what changed and why; what typed it is not part of the record.

Not modesty and not about hiding the tool. A trailer is a claim on authorship, and a tool that signs what it did for somebody is taking a share of something it was hired to produce; the honest signature is the diff, already in the record and already attributable. The practical half matters too — these lines outlive the session, ending up in release notes, git shortlog, and every downstream fork.

Harness defaults push the other way, so TestNoTemplateShipsAnAttributionFooter keeps any template from modelling what the rule forbids (Co-Authored-By without a colon is allowed in exactly one place: the sentence forbidding it). It binds scc's own history first — recorded in CLAUDE.md, and this PR is the first one written under it.

Budget

Template versions 20 and 21. delivery.md absorbs both changes at its 62-line grandfathered cap: the git switch -c fenced one-liner goes inline, and §8's argument is tightened to prose the design doc holds in full anyway. plan-run records the delivery at step 4 — it is the loop that opens the most branches.

Checks

gofmt -l clean · go vet ./... clean · go test ./... green · golangci-lint run → 0 issues. The git-backed lifecycle test skips where git is not on PATH; the reconcile policy is a pure function and is tested without a repository.

…ution out of the record

Two halves of one question — what the record of a delivery says.

**The spec records where it is being built.** A branch was the one part of this
methodology that left no trace in the artifacts: the spec said which boxes were
ticked, git said a branch had been unmerged for three weeks, and nothing joined
the two, so "which of these actually shipped" was answerable only by somebody
holding both halves. Under `autonomy: auto` that is nobody.

Three keys on requirements.md, beside the kickoff answers:

  branch: feat/user-auth
  pr: 28
  delivery: in-progress | in-review | merged | abandoned

The vocabulary is closed for the reason a task's flags are — an open one produces
three spellings of "done" inside a month, and what is still unfinished is exactly
the question a synonym destroys. Absent is the fifth state and needs no name, so
the validator grades these only when present and every existing spec keeps passing.
`delivery:` rather than `status:`, which already means the approval seal on a plan.

- `scc spec track <feature> --here|--branch|--pr|--delivery` records what the caller
  knows. `scc spec sync` derives what git and the forge know, over every spec, under
  the same verify-and-roll-back contract as `scc patch`. `scc spec list` and
  `spec show` carry the record.
- Neither guesses. A deleted branch with no PR to ask about is reported undetermined
  and left as it was: merged and abandoned are indistinguishable once the ref is gone.
- Merged is not "is an ancestor of the base" — a branch created ten seconds ago
  satisfies that, and the first run declared a spec delivered before a line of it
  existed. It is ahead == 0 with behind > 0, and the fast-forward case no ref can
  resolve is called not merged: this record exists to surface unfinished work, so the
  error that leaves a loose end visible is the one to make.
- internal/git is the fourth integration package. git and gh together because the
  question has two halves and no caller should route between them; nothing in it
  installs and nothing writes, which is what makes running it over a whole workspace
  safe by construction.
- Plans are out for now for a naming reason: `pr:` on a plan already means the
  delivery shape plan-run asks for at kickoff.

**The record carries no attribution.** No Co-Authored-By for an assistant, no session
link, no generated-with footer, no naming of a model, vendor, or harness — in a commit
message, a PR title or body, or a branch name. The work is the user's: a tool that
signs what it did for somebody is claiming a share of it, and the honest signature is
the diff. These lines also outlive the session that wrote them, ending up in release
notes and every downstream fork. Harness defaults push the other way, so the rule
states it and TestNoTemplateShipsAnAttributionFooter keeps any template from modelling
what the rule forbids. It binds scc's own history first, recorded in CLAUDE.md.

Template versions 20 and 21; delivery.md absorbs both at its 62-line cap by tightening
the argument the design doc holds in full.
@protonspy
protonspy merged commit 8135634 into main Aug 27, 2026
4 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.

1 participant