check-issue-refs: catch ambiguous owner#N cross-repo refs in markdown too

This commit is contained in:
damocles 2026-09-09 20:21:35 +02:00
commit 786ab304d6
9 changed files with 64 additions and 39 deletions

View file

@ -92,7 +92,7 @@ declarations.
- **What does a specific Rust crate do, on its own terms?** - **What does a specific Rust crate do, on its own terms?**
[`crates/`](crates/README.md) — every workspace crate's own [`crates/`](crates/README.md) — every workspace crate's own
`README.md`, one level up from source (hyperhive#3051); the crate `README.md`, one level up from source (#3051); the crate
itself is still the source of truth, this is just a walkable mirror. itself is still the source of truth, this is just a walkable mirror.
## Process & conventions ## Process & conventions

View file

@ -4,7 +4,7 @@ Every agent has a place in an operator-editable parent/child tree, used
to scope which agents can manage which others. This doc covers how to scope which agents can manage which others. This doc covers how
hive-c0re stores and edits the tree today, the rules that are meant to run on top hive-c0re stores and edits the tree today, the rules that are meant to run on top
of it once enforcement is finished, and where the manager still gets of it once enforcement is finished, and where the manager still gets
special-cased in the meantime. Tracking issue: hyperhive#361 special-cased in the meantime. Tracking issue: #361
(`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/361`). (`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/361`).
## Where the tree lives ## Where the tree lives

View file

@ -3,7 +3,7 @@
One page per Rust workspace crate — its own `README.md`, unchanged, served One page per Rust workspace crate — its own `README.md`, unchanged, served
here for browsing alongside the rest of the docs site. **The crate's own here for browsing alongside the rest of the docs site. **The crate's own
`README.md` is the source of truth; nothing here is hand-maintained.** `README.md` is the source of truth; nothing here is hand-maintained.**
`nix/packages/reference-docs.nix` (hyperhive#3051) generates every page at `nix/packages/reference-docs.nix` (#3051) generates every page at
build time straight from the crate's real `README.md`, so it can never build time straight from the crate's real `README.md`, so it can never
drift out of sync the way a hand-copied mirror would — edit the crate's drift out of sync the way a hand-copied mirror would — edit the crate's
own README to change what shows up here. own README to change what shows up here.

View file

@ -95,7 +95,7 @@ store's container reads it, writes the `swarm-controller` policy, enables the
cert auth method, and creates the `swarm-controller` role that attaches the two. cert auth method, and creates the `swarm-controller` role that attaches the two.
⚠️ **This has only run once, and it didn't go all the way through.** The ⚠️ **This has only run once, and it didn't go all the way through.** The
first real provision against a live store returned 403 (hyperhive#4124), so first real provision against a live store returned 403 (#4124), so
treat the block above as derived-then-partly-tested rather than proven: the treat the block above as derived-then-partly-tested rather than proven: the
grants still come from `-output-policy`, not from a swarm that came up on them. grants still come from `-output-policy`, not from a swarm that came up on them.
What that 403 does _not_ tell you is whether the login or the write was What that 403 does _not_ tell you is whether the login or the write was

View file

@ -85,7 +85,7 @@ structurally rather than for one specific trigger. Two columns:
`agent_links()` in hive-agent is the single source of truth for `agent_links()` in hive-agent is the single source of truth for
both. No separate overflow (`⋯`) menu exists any more — it used both. No separate overflow (`⋯`) menu exists any more — it used
to hold exactly this dashboard link plus a rebuild-container action to hold exactly this dashboard link plus a rebuild-container action
(mara, hyperhive#3704: "remove rebuild button, move link to (mara, #3704: "remove rebuild button, move link to
dashboards into links menu") — rebuild had no real discoverability dashboards into links menu") — rebuild had no real discoverability
need of its own (the dashboard's own R3BU1LD button already covers need of its own (the dashboard's own R3BU1LD button already covers
it) so it's gone outright, and the dashboard link moved here, it) so it's gone outright, and the dashboard link moved here,

View file

@ -236,7 +236,7 @@ than reconstructing chrome hive-c0re no longer sends. Settled entries
render their **full step tree**, not just a bare summary — the wire render their **full step tree**, not just a bare summary — the wire
doesn't filter `Done` nodes out. doesn't filter `Done` nodes out.
**State filter (hyperhive#2606).** A row of per-state checkboxes above **State filter (#2606).** A row of per-state checkboxes above
the tree — one per lifecycle state, matching the row glyphs — lets the the tree — one per lifecycle state, matching the row glyphs — lets the
operator narrow which root groups render; unchecking a state re-fetches operator narrow which root groups render; unchecking a state re-fetches
`GET /api/jobq/graph?states=<checked, comma-joined>` rather than `GET /api/jobq/graph?states=<checked, comma-joined>` rather than
@ -811,7 +811,7 @@ below).
### Status badges ### Status badges
**Which single badge (hyperhive#3139):** `ContainerView.failed` **Which single badge (#3139):** `ContainerView.failed`
(systemd `ActiveState=failed` — the unit exhausted its bounded (systemd `ActiveState=failed` — the unit exhausted its bounded
restarts and gave up on its own) draws a red `✖ gave up` badge; restarts and gave up on its own) draws a red `✖ gave up` badge;
otherwise a plain muted `■ not running` — a container an operator otherwise a plain muted `■ not running` — a container an operator

View file

@ -244,7 +244,7 @@ render, `restoreOpenDetails()` re-applies after. Long-content
drill-ins (file previews, diffs, journald logs) now open in the drill-ins (file previews, diffs, journald logs) now open in the
**side panel** (see below) rather than expanding inline. The **side panel** (see below) rather than expanding inline. The
dashboard's own answered-questions history list was the only dashboard's own answered-questions history list was the only
`<details data-restore-key>` consumer and is gone (hyperhive#3721) `<details data-restore-key>` consumer and is gone (#3721)
`snapshotOpenDetails()` / `restoreOpenDetails()` currently have `snapshotOpenDetails()` / `restoreOpenDetails()` currently have
nothing to restore, kept in place for the next collapsible section nothing to restore, kept in place for the next collapsible section
that needs it rather than ripped out along with their one caller. that needs it rather than ripped out along with their one caller.

View file

@ -55,7 +55,7 @@ sets a per-call cwd, so with `services.hyperhive.c0re.buildSlots > 1`,
two concurrent calls (any mix of `create`/`update`) could share that one two concurrent calls (any mix of `create`/`update`) could share that one
`.tmp`: one's `readlink(".tmp")` resolving to the _other's_ build `.tmp`: one's `readlink(".tmp")` resolving to the _other's_ build
output, handing an agent's container the wrong agent's closure — the output, handing an agent's container the wrong agent's closure — the
"agent container gets closure of other agent" bug (hyperhive#3312). "agent container gets closure of other agent" bug (#3312).
Building the toplevel here and passing the resolved store path via Building the toplevel here and passing the resolved store path via
`--system-path` for _every_ call means `buildFlake()` never runs at all, `--system-path` for _every_ call means `buildFlake()` never runs at all,

View file

@ -1,47 +1,72 @@
#!/bin/sh #!/bin/sh
# CI lint: flags tracker tags (a hash followed by an issue number) in # CI lint, two passes over tracked files. Emits a CI error annotation per
# source comments. The hive convention is prose, not tracker tags, in # hit, exits 1 if either pass finds something. Its own required CI job
# code (see /knowledge/hive-rules.md) — tags rot, they point at moving # (branch protection) — a hit blocks merge.
# targets and leak tracker coupling into the source tree.
# #
# Emits a CI error annotation per hit and exits 1 if any tag is found, # 1. Source comments (`.rs .nix .js .ts .tsx .css .html`): flags ANY tracker
# 0 otherwise. It runs as its own CI job and IS a required check on the # tag (hash + issue number) outright — the hive convention is prose, not
# forge (branch protection) — a hit blocks merge. # tracker tags, in code (/knowledge/hive-rules.md); tags rot. Markdown is
# exempt here (prose docs may cite the tracker with a bare `#N`). Pattern:
# a hash, 2-5 digits, then non-alphanumeric-or-EOL — skips letter-bearing
# hex colours and digit-runs-then-letter (`#24h`). Residual: a pure-
# numeric short hex trips it — write the six-digit form to dodge.
# #
# Scope: tracked *.rs *.nix *.js *.ts *.tsx *.css *.html. Markdown is exempt # 2. Markdown (`.md`): narrower — only an AMBIGUOUS cross-repo-shaped ref, a
# (prose docs may legitimately cite the tracker). The pattern matches a # word glued directly to `#N` with no `/` (`hyperhive#1234`). A bare
# hash, 2-5 digits, then a non-alphanumeric char or end-of-line. A real # `#1234` or a qualified `owner/repo#1234` stay legal; only the glued,
# tracker tag is never glued to a letter, so the trailing class skips # unqualified `owner#N` — indistinguishable from a real cross-repo
# both letter-bearing / 6-8-digit hex colours (the digit run breaks or # citation without checking whether `owner` is a live org — gets caught
# overruns) and digit-runs followed by a letter — e.g. hash-route # (the forge's public mirror carries no issue/PR data, so this is dead
# fragments like #24h. Residual: a pure-numeric short hex (e.g. three # weight for a public reader regardless of which repo it meant). The
# identical digits) trips it — write the six-digit form to dodge. # glued prefix must start lowercase (real org/repo slugs are, see
# `hive_types::Ident`) — rules out `PR#4124`/`Hyperhive#1234`. Residual:
# an ordinary lowercase word glued to a number (`line#123`) still
# matches — no live-allowlist option in a dependency-free script; rare in
# practice, same escape hatch as pass 1.
# #
# Escape hatch: a line containing the marker `lint:allow` is exempt. # Escape hatch (both passes): a line with the marker `lint:allow` is exempt.
# Reserve it for genuine `#<digits>` that aren't tracker tags — e.g. a # Reserve it for a genuine non-tag hit and keep a short reason next to it —
# `#123` markdown-heading example or hash-prefixed test-input data — # not for a real tracker tag or ambiguous ref, rewrite those instead.
# and keep a short reason next to the marker. Don't use it to keep a
# real tracker tag; rewrite those to prose.
set -eu set -eu
pattern='#[0-9]{2,5}([^0-9a-zA-Z]|$)' src_pattern='#[0-9]{2,5}([^0-9a-zA-Z]|$)'
md_pattern='(^|[^0-9A-Za-z._/-])[a-z][a-z0-9-]*#[0-9]{2,5}([^0-9a-zA-Z]|$)'
# `/dev/null` forces grep to always print a filename prefix, even when # `/dev/null` forces grep to always print a filename prefix, even when
# xargs hands it a single file. `-r`/`-0` keep it robust to odd paths # xargs hands it a single file. `-r`/`-0` keep it robust to odd paths and
# and an empty file list. Lines carrying the `lint:allow` marker are # an empty file list. Lines carrying the `lint:allow` marker are dropped
# dropped (legitimate non-tracker `#<digits>`; see the header). # (legitimate non-tracker hit; see the header).
hits="$( src_hits="$(
git ls-files -z '*.rs' '*.nix' '*.js' '*.ts' '*.tsx' '*.css' '*.html' \ git ls-files -z '*.rs' '*.nix' '*.js' '*.ts' '*.tsx' '*.css' '*.html' \
| xargs -0 -r grep -nE "$pattern" /dev/null 2>/dev/null \ | xargs -0 -r grep -nE "$src_pattern" /dev/null 2>/dev/null \
| grep -v 'lint:allow' || true
)"
md_hits="$(
git ls-files -z '*.md' \
| xargs -0 -r grep -nE "$md_pattern" /dev/null 2>/dev/null \
| grep -v 'lint:allow' || true | grep -v 'lint:allow' || true
)" )"
if [ -n "$hits" ]; then count=0
echo "$hits" | while IFS=: read -r file lineno _; do
if [ -n "$src_hits" ]; then
echo "$src_hits" | while IFS=: read -r file lineno _; do
printf '::error file=%s,line=%s::tracker tag in source — write prose, not a hash-number tag (see /knowledge/hive-rules.md)\n' "$file" "$lineno" printf '::error file=%s,line=%s::tracker tag in source — write prose, not a hash-number tag (see /knowledge/hive-rules.md)\n' "$file" "$lineno"
done done
count="$(printf '%s\n' "$hits" | wc -l | tr -d ' ')" src_count="$(printf '%s\n' "$src_hits" | wc -l | tr -d ' ')"
printf 'check-issue-refs: %s tracker tag(s) found in source\n' "$count" >&2 count=$((count + src_count))
fi
if [ -n "$md_hits" ]; then
echo "$md_hits" | while IFS=: read -r file lineno _; do
printf '::error file=%s,line=%s::ambiguous cross-repo reference — a bare `#N` or a fully qualified `owner/repo#N` are fine, but this looks like an unqualified `owner#N`; spell out the repo or clarify in prose\n' "$file" "$lineno"
done
md_count="$(printf '%s\n' "$md_hits" | wc -l | tr -d ' ')"
count=$((count + md_count))
fi
if [ "$count" -gt 0 ]; then
printf 'check-issue-refs: %s hit(s) found\n' "$count" >&2
exit 1 exit 1
fi fi
exit 0 exit 0