Repository navigation
fix(ownership-scan): a bare token is ownership in a declaration, not in prose - #10
Merged
antoinetoussaint-byte merged 2 commits intoOct 27, 2026
Merged
Conversation
…in prose `Repository branch binding` ended its alternation with the bare token `Branch` and `Repository revision binding` with the bare `Revision`; the scan greps case-insensitively, so either English word in any comment in a scannable file failed the guard. Seen on codefly-dev/service-nextjs#186, where the one finding was FORBIDDEN [Repository branch binding] runtime.go:1221: // Resolved before the container branch: a lock ... about an `if` below that line. A gate that reports a word rather than an ownership concept teaches the next author to reword a comment until it goes quiet, which is the move this guard exists to prevent. Both bare tokens now have to appear in a declaration: a manifest key or list entry, an assignment, a field declaration, or a struct tag. The explicit spellings (repoBranch, gitBranch, targetRevision, repository-revision, …) and refs/heads/ stay matched wherever they appear, and a selector read stays allowed — as `.Revision` already was, and now `.Branch` too, consistently. Also repairs a dead assertion found beside it, under the scout rule. The contract-field case read `if out="bash: : No such file or directory"` — a literal assignment, always true — so the suite reported "reading a released contract revision field is not ownership" without ever running the scan: a gate test green about a name rather than about the gate. Restored, it passes for the right reason, and the fixture it protects is now actually scanned. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… start
Review of the first commit found it wrong in both directions, and the second
one matters more than the first.
A comment that QUOTED either word still red: the quoted-key alternative was a
bare `"branch"` with no colon, so
// The caller names the "branch" it took, and the "revision" it saw.
produced FORBIDDEN for both tokens — the very class this change exists to
remove. The prose fixture passed only because it never quoted either word.
And requiring a declaration at line start caught far less than the first
commit's message claimed. All of these scanned CLEAN:
cfg.Branch = "main"
cfg.Revision = "HEAD"
cfg.SetBranch(s.Branch)
Source{Branch: "main", Revision: "HEAD"}
Only a bare identifier at line start was caught — `Branch = s.Branch`, which
nobody writes in Go. So `cfg.Branch = branchFromManifest()`, a real repository
binding, passed a guard that main's bare-token pattern would have caught: the
change narrowed the gate in one direction and weakened it in the other.
The rule is now that a bare token is ownership where the SYNTAX binds it — a
manifest key or list entry, a quoted key (with the colon that makes it one), a
struct tag, a field declaration, a selector assignment, a struct-literal
field, a setter or builder call, a bare assignment. A plain read stays
allowed: `[.>]token[[:space:]]*=[^=]` does not match `req.Revision == ""`.
Fixtures pin both directions, so the suite fails without this: the prose
fixture now quotes both words, and token-declaration-forms carries every
binding spelling. Against the first commit's patterns the suite reports 6
failures — one prose false positive and five missed bindings.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
A fix in the gate that owns the behaviour.
manifest-ownership-scanreported an Englishword rather than an ownership concept, so a comment explaining control flow failed the guard.
Repository branch bindingended its alternation with the bare tokenBranch, andRepository revision bindingwith the bareRevision. The scan greps case-insensitively, soeither word, standing alone anywhere in a scannable file, was a violation. On
codefly-dev/service-nextjs#186 the single finding was:
That line is about an
ifbelow it. No repository is bound anywhere near it.The rule, stated once for both tokens
A bare token is ownership where the syntax BINDS it. Matched: a manifest key or list entry,
a quoted key with the colon that makes it a key, a struct tag, a field declaration, a selector
assignment (
cfg.Branch = "main"), a struct-literal field (Source{Branch: ...}), a setter orbuilder call (
cfg.SetBranch(...)), and a bare assignment. Unchanged:repoBranch,repositoryBranch,repository-branch,gitBranch,targetRevision,repository_revision,gitRevision, … — still match wherever theyappear, including in a comment, where they are a real smell;
refs/heads/still matches anywhere;[.>]token[[:space:]]*=[^=]does not matchreq.Revision == "": reading a released contract field owns neither the type nor a binding..Revisionalready had this;.Branchnow does too, which removes an inconsistency betweenthe two patterns rather than introducing one.
The first version of this PR was wrong in both directions
Kept here because it is the same class the PR is correcting, and the second error is the worse
one. Review found:
"branch"with no colon, so// The caller names the "branch" it took, and the "revision" it saw.producedFORBIDDENfor both tokens. The prose fixture passed only because it neverquoted either word.
cfg.Branch = "main",cfg.Revision = "HEAD",cfg.SetBranch(s.Branch)andSource{Branch: "main", Revision: "HEAD"}all scanned clean. Only a bare identifier atline start was caught —
Branch = s.Branch, which nobody writes in Go. Socfg.Branch = branchFromManifest(), a real repository binding, passed a guard that main'sbare-token pattern would have caught. The change narrowed the gate in one direction and
weakened it in the other.
Both are fixed above, and both are now pinned by fixtures rather than by argument.
Evidence, both directions
From the action's own suite plus the case that triggered this:
The suite fails without this fix. Against the first commit's patterns it reports 6
failures — one prose false positive and five missed bindings — which is the check the first
version lacked.
The original trigger still holds:
runtime.goat service-nextjsb2986f6, carrying theoriginal
// Resolved before the container branch:comment that this guard rejected, scansclean. So this addresses the real case, not a reworded version of it.
The service-nextjs reword is a dated stopgap. That repo's
0d82b4brenamed the comment to"the container check below" purely to get past this scan, and said so. Once this lands, the
reword is unnecessary and the comment can say "branch" again.
Also here: a gate test that reported a verdict without ever running the gate
Found beside the pattern and fixed under the scout rule. The contract-field case read:
That is a literal assignment, so the condition is always true. The suite printed a verdict about
the scan without ever invoking it, and the
testdata/contract-field/fixture had never beenscanned — the
green-about-a-nameclass, inside the tests of a guard whose whole purpose is tobe trusted. Restored to run the scan, it passes for the right reason:
req.Revisionis aselector read the pattern permits. Had it been live, it would also have caught this PR
regressing that case.
Reuse
No new mechanism: one pattern string per token, the existing
PATTERNSloop, the existingfixture-directory convention, and the existing
scan_test.shharness. Deliberately not written:a comment-stripping pass over source files (a second parser for a job a shape anchor does), and
a path or inline exclusion (the action rejects those for runtime source on purpose, and a
per-repo opt-out would hide real bindings).
🤖 Generated with Claude Code
Fleet-wide trap found while doing this, worth stating here
The private-git wrapper these agents use pipes
gitthroughsedto mask tokens, so thepipeline's exit status is
sed's, not git's.git ls-remote --exit-codethrough it alwaysreports success, and a tag-existence check built on
$?reported a tag that did not exist. Anycheck through such a wrapper must test output, never
$?. Same class as this PR's finding:a check that is green about something other than what it claims to measure.