check-issue-refs: blanket-ban tracker tags in markdown too, no exceptions

This commit is contained in:
damocles 2026-09-09 21:02:48 +02:00
commit e1e913015d
24 changed files with 81 additions and 89 deletions

View file

@ -92,7 +92,8 @@ 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 (#3051); the crate `README.md`, one level up from source
(`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/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,8 +4,8 @@ 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: #361 special-cased in the meantime. Tracking issue:
(`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/361`). `$HIVE_FORGE_URL/hyperhive/hyperhive/issues/361`.
## Where the tree lives ## Where the tree lives

View file

@ -69,7 +69,8 @@ request.
and no approval. (it's currently mounted read-write, which is a and no approval. (it's currently mounted read-write, which is a
defect tracked separately, not an authoring path.) defect tracked separately, not an authoring path.)
Branch protection (push/merge allowlist = `core`, approvals allowlist Branch protection (push/merge allowlist = `core`, approvals allowlist
= operator team; see "Forge mirror" and #1787) makes the agent a = operator team; see "Forge mirror" and
`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/1787`) makes the agent a
write collaborator that **can't merge its own config PR**. write collaborator that **can't merge its own config PR**.
2. hive-c0re's `/webhook/config-pr` endpoint receives the Forgejo 2. hive-c0re's `/webhook/config-pr` endpoint receives the Forgejo
`pull_request` event (opened / synchronized / reopened) and queues a `pull_request` event (opened / synchronized / reopened) and queues a
@ -476,7 +477,8 @@ repo — so it can push a branch and open a config PR — but not a member
of any other agent's, so it can't reach another agent's config through of any other agent's, so it can't reach another agent's config through
the forge. Branch protection keeps `main` push/merge `core`-only with the forge. Branch protection keeps `main` push/merge `core`-only with
operator-team approval, so an agent can't fast-forward its own config or operator-team approval, so an agent can't fast-forward its own config or
self-merge its PR (see the End-to-end flow + #1787). hive-c0re passes the tokenised push self-merge its PR (see the End-to-end flow +
`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/1787`). hive-c0re passes the tokenised push
URL inline to `git push`, never writing it into URL inline to `git push`, never writing it into
`applied/<n>/.git/config`; that repo is RO-bind-mounted into the root `applied/<n>/.git/config`; that repo is RO-bind-mounted into the root
agent, and a stored token would leak core's admin credential to an agent, and a stored token would leak core's admin credential to an

View file

@ -437,7 +437,7 @@ would only be transient.
On a btrfs host, `lifecycle::ensure_agent_state_subvolume` creates a brand-new agent's state root as a On a btrfs host, `lifecycle::ensure_agent_state_subvolume` creates a brand-new agent's state root as a
**btrfs subvolume** instead of a plain directory (progressive **btrfs subvolume** instead of a plain directory (progressive
enhancement — see the #1762 lane). This is a no-op fallback on enhancement — see `$HIVE_FORGE_URL/hyperhive/hyperhive/issues/1762`). This is a no-op fallback on
non-btrfs hosts and for any agent whose root already exists, so non-btrfs hosts and for any agent whose root already exists, so
nothing is automigrated: existing agents keep their plain dirs nothing is automigrated: existing agents keep their plain dirs
until an explicit opt-in upgrade. until an explicit opt-in upgrade.

View file

@ -3,7 +3,8 @@
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` (#3051) generates every page at `nix/packages/reference-docs.nix`
(`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/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,8 @@ 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 (#4124), so first real provision against a live store returned 403
(`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/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
@ -128,7 +129,8 @@ can't hand you, being what opens it. ⚠️ Not the gateway's HTTPS certificates
store that took its identity from an authority it will itself distribute could store that took its identity from an authority it will itself distribute could
never come up before that authority. never come up before that authority.
Making even the `init` unnecessary is tracked in issue #3768. Making even the `init` unnecessary is tracked in
`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/3768`.
### 4 · Swarm SSO (only when `deploy.authelia`) ### 4 · Swarm SSO (only when `deploy.authelia`)

View file

@ -215,7 +215,7 @@ pipeline steps in order:
`CommonMark` ATX heading (1-6 leading `#`s followed by a space, `CommonMark` ATX heading (1-6 leading `#`s followed by a space,
tab, or end-of-line), prepend `\` so the embedded body doesn't tab, or end-of-line), prepend `\` so the embedded body doesn't
blow into a top-level h1/h2 inside the wrapper message when the blow into a top-level h1/h2 inside the wrapper message when the
dashboard renders it. Lines like `#tag`, `#123`, `#!/bin/bash` dashboard renders it. Lines like `#tag`, `#123`, `#!/bin/bash` <!-- lint:allow: syntax examples, not real tags -->
**aren't** headings — no escape, no cosmetic noise. Indented **aren't** headings — no escape, no cosmetic noise. Indented
"headings" inside lists / nested quotes keep their leading "headings" inside lists / nested quotes keep their leading
whitespace. whitespace.

View file

@ -64,7 +64,8 @@ hive-c0re owns the host admin socket.
is the wake-event-injection surface. Recipient is implicit — the is the wake-event-injection surface. Recipient is implicit — the
agent the socket belongs to — and `from` is caller-chosen so the agent the socket belongs to — and `from` is caller-chosen so the
wake prompt can label the source verbatim (`"matrix: new message in wake prompt can label the source verbatim (`"matrix: new message in
#general"`, `"forge: PR #42 opened"`, etc.). Typical caller: an #general"`, `"forge: PR #42 opened"`, etc.). <!-- lint:allow: example strings, not real tags -->
Typical caller: an
in-container background task (the matrix daemon, a scraper, the in-container background task (the matrix daemon, a scraper, the
forge-notify webhook subscriber) that needs to signal "external work forge-notify webhook subscriber) that needs to signal "external work
has arrived" without going through the broker as a peer agent. has arrived" without going through the broker as a peer agent.

View file

@ -412,7 +412,7 @@ connects to the compositor at `127.0.0.1:<vnc_port>`.
Wayland clients couldn't find the compositor — libwayland falls Wayland clients couldn't find the compositor — libwayland falls
back to a headless display or errors out, the app "works" on a back to a headless display or errors out, the app "works" on a
second invisible display, and the VNC session shows a blank weston second invisible display, and the VNC session shows a blank weston
desktop (#540 double-screen). desktop (`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/540`, double-screen).
- **VNC bind address**: Weston's `vnc-backend` has no CLI - **VNC bind address**: Weston's `vnc-backend` has no CLI
bind-address flag (unlike the `rdp-backend`'s `--address`), so the bind-address flag (unlike the `rdp-backend`'s `--address`), so the
listener binds `0.0.0.0`. The harness relay only connects via listener binds `0.0.0.0`. The harness relay only connects via

View file

@ -112,7 +112,7 @@ summary change signals the harness turn loop directly (in-process, no broker
round-trip), so the harness drives a turn for the agent to handle it via round-trip), so the harness drives a turn for the agent to handle it via
`get_loose_ends`, then clears the todo with `cancel_loose_end(kind: "todo", id: N)` (dials the `get_loose_ends`, then clears the todo with `cancel_loose_end(kind: "todo", id: N)` (dials the
in-container socket directly — no bash task involved, so clearing doesn't in-container socket directly — no bash task involved, so clearing doesn't
spawn another todo; see #2639). Same mechanism the matrix daemon uses for spawn another todo; see `$HIVE_FORGE_URL/hyperhive/hyperhive/issues/2639`). Same mechanism the matrix daemon uses for
unread rooms. An inline `wait_seconds` / `status` observation that already unread rooms. An inline `wait_seconds` / `status` observation that already
delivered the result instead clears the keyed todo, so no redundant delivered the result instead clears the keyed todo, so no redundant
loose-end follows. loose-end follows.

View file

@ -66,12 +66,12 @@ hive-forge comment-edit 18042 --body "..." # edit a comment
hive-forge assign 42 damocles hive-forge assign 42 damocles
hive-forge close 42 hive-forge close 42
hive-forge labels 42 add feature hive-forge labels 42 add feature
hive-forge issue dependency 42 # list #42's dependencies (issues it's blocked by) hive-forge issue dependency 42 # list issue 42's dependencies (issues it's blocked by)
hive-forge issue dependency 42 add 40 41 # #42 is now blocked by #40 and #41 hive-forge issue dependency 42 add 40 41 # issue 42 is now blocked by 40 and 41
hive-forge issue dependency 42 remove 40 # drop the #40 dependency link hive-forge issue dependency 42 remove 40 # drop the dependency on 40
hive-forge pr dependency 55 add 42 # same verb, PR-scoped (Forgejo shares the issue/PR index) hive-forge pr dependency 55 add 42 # same verb, PR-scoped (Forgejo shares the issue/PR index)
hive-forge issue reaction 42 # list #42's reactions hive-forge issue reaction 42 # list issue 42's reactions
hive-forge issue reaction 42 add heart # react to #42 with :heart: hive-forge issue reaction 42 add heart # react to issue 42 with :heart:
hive-forge issue reaction 42 --comment 18042 add +1 # react to a specific comment instead hive-forge issue reaction 42 --comment 18042 add +1 # react to a specific comment instead
hive-forge issue reaction 42 remove heart # remove your own :heart: reaction hive-forge issue reaction 42 remove heart # remove your own :heart: reaction
hive-forge issue reaction 1 --list-allowed # this instance's actual configured shortcodes hive-forge issue reaction 1 --list-allowed # this instance's actual configured shortcodes
@ -202,7 +202,7 @@ verdict alone.
``` ```
hive-forge pr-status --pr 42 hive-forge pr-status --pr 42
# PR #42: feat(...): ... # PR #42: feat(...): ... # lint:allow: sample CLI output, not a real tag
# state: open (mergeable: yes) # state: open (mergeable: yes)
# CI: e39a87ea3949: ✅ success (1 context(s)) # CI: e39a87ea3949: ✅ success (1 context(s))
# ✅ CI / nix flake check (pull_request): success — Successful in 1m50s # ✅ CI / nix flake check (pull_request): success — Successful in 1m50s

View file

@ -63,7 +63,7 @@ What still holds: the grant is **read-only** (a hive can't write an agent's
credential, so it can't hand itself an agent's identity), and it's scoped to credential, so it can't hand itself an agent's identity), and it's scoped to
the agent-credential prefix — nothing else in the store is reachable with it. the agent-credential prefix — nothing else in the store is reachable with it.
**#4137** tracks narrowing it, with the two candidate directions: scope **`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/4137`** tracks narrowing it, with the two candidate directions: scope
the grant per hive (and pay for the re-emission), or give each agent container the grant per hive (and pay for the re-emission), or give each agent container
its own store identity so credentials never pass through a hive at all. its own store identity so credentials never pass through a hive at all.

View file

@ -133,8 +133,9 @@ the reactive path only fires once claude-code has already refused a whole
turn as too long. Neither can see context growth happening _inside_ a turn as too long. Neither can see context growth happening _inside_ a
single long turn's own tool-calling before it returns — a turn whose tool single long turn's own tool-calling before it returns — a turn whose tool
calls alone blow past the window hits the reactive path with no preceding calls alone blow past the window hits the reactive path with no preceding
checkpoint, losing whatever wasn't already persisted (#2707, root-caused in checkpoint, losing whatever wasn't already persisted
#3727). (`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/2707`, root-caused in
`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/3727`).
**Safety net for that gap: claude-code's own native autocompact is on**, **Safety net for that gap: claude-code's own native autocompact is on**,
via the managed settings (`"autoCompactEnabled": true`, via the managed settings (`"autoCompactEnabled": true`,

View file

@ -222,7 +222,7 @@ operator-driven `hivectl choom` session — which passes neither `--tools`
nor `--allowedTools` — gets claude's built-in synchronous `Bash` tool nor `--allowedTools` — gets claude's built-in synchronous `Bash` tool
(inline, human-approved). That sidesteps the async `mcp__bash__run` (inline, human-approved). That sidesteps the async `mcp__bash__run`
completion wake landing in the wrong session (the harness inbox) for a completion wake landing in the wrong session (the harness inbox) for a
choom-started task (#2356); `choom` is an operator (root) action, so choom-started task (`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/2356`); `choom` is an operator (root) action, so
built-in shell there stays within the existing trust boundary. The bash MCP server built-in shell there stays within the existing trust boundary. The bash MCP server
(`run` / `status` / `kill`) uses `allowedTools = ["*"]` so all (`run` / `status` / `kill`) uses `allowedTools = ["*"]` so all
`mcp__bash__*` tools are always available regardless of tool groups. `mcp__bash__*` tools are always available regardless of tool groups.

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, #3704: "remove rebuild button, move link to (mara, `$HIVE_FORGE_URL/hyperhive/hyperhive/issues/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

@ -67,7 +67,7 @@ default palette). Default hexes shown are Catppuccin Mocha.
| `--blue` | `base0D` | `#89b4fa` | links, interactive accent (distinct from cyan) | | `--blue` | `base0D` | `#89b4fa` | links, interactive accent (distinct from cyan) |
| `--purple` | `base0E` | `#cba6f7` | accent — active tabs, links, highlights | | `--purple` | `base0E` | `#cba6f7` | accent — active tabs, links, highlights |
| `--pink` | `base0F` | `#f5c2e7` | thinking events | | `--pink` | `base0F` | `#f5c2e7` | thinking events |
| `--crust` | `mix(base00 58%, #000)` | `#11111b` | terminal / code block background (below `--bg`) | | `--crust` | `mix(base00 58%, #000000)` | `#11111b` | terminal / code block background (below `--bg`) |
| `--muted` | `mix(base05 55.5%, base00)` | `#7f849c` | secondary / dimmed text | | `--muted` | `mix(base05 55.5%, base00)` | `#7f849c` | secondary / dimmed text |
| `--subtext0` | `mix(base05 77.7%, base00)` | `#a6adc8` | toolbar/status text; dimmer than `--fg`, lighter than `--muted` | | `--subtext0` | `mix(base05 77.7%, base00)` | `#a6adc8` | toolbar/status text; dimmer than `--fg`, lighter than `--muted` |

View file

@ -146,7 +146,7 @@ part-way through a spawn is byte-identical on disk to a tombstone, and
the pane lists both. The pane carries a standing warning to that effect, and the pane lists both. The pane carries a standing warning to that effect, and
the row badge says `offline` rather than `destroyed` — the absence of a the row badge says `offline` rather than `destroyed` — the absence of a
container is all hive-c0re can actually prove. Fixing it properly container is all hive-c0re can actually prove. Fixing it properly
needs a recorded destroy: **#3020**, deferred to the swarm-controller / needs a recorded destroy: **`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/3020`**, deferred to the swarm-controller /
snapshot-storage rework where the problem changes shape. snapshot-storage rework where the problem changes shape.
*Stale permission entries*: agents with explicit capability or *Stale permission entries*: agents with explicit capability or
@ -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 (#2606).** A row of per-state checkboxes above **State filter (`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/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 (#3139):** `ContainerView.failed` **Which single badge (`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/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,8 @@ 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 (#3721) `<details data-restore-key>` consumer and is gone
(`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/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

@ -28,6 +28,6 @@ additions are layered on top of the default agent dist via the
output is reproducible without network access at build time. output is reproducible without network access at build time.
- **esbuild**: vanilla-JS bundler, no framework runtime overhead. - **esbuild**: vanilla-JS bundler, no framework runtime overhead.
Each workspace's `build.mjs` is ~30 lines. Each workspace's `build.mjs` is ~30 lines.
- **Single-PR migration**: see issue #273 for the design proposal and - **Single-PR migration**: see `$HIVE_FORGE_URL/hyperhive/hyperhive/issues/273` for the design proposal and
the four-commit shape (npm scaffold → nix derivations → container the four-commit shape (npm scaffold → nix derivations → container
plumbing → Rust cutover). plumbing → Rust cutover).

View file

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

View file

@ -43,7 +43,8 @@ It used to be otherwise, and the seam was real: each side had its own
`users.json` treated as authoritative, rendering the _same_ physical `users.json` treated as authoritative, rendering the _same_ physical
`users.yml`. A writer whose own JSON was missing could not tell "nothing `users.yml`. A writer whose own JSON was missing could not tell "nothing
here yet" from "someone else's users", so it refused to write at all — here yet" from "someone else's users", so it refused to write at all —
which is exactly what a hive with existing users hit (#3422). which is exactly what a hive with existing users hit
(`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/3422`).
What still has to hold, since two processes share the file: What still has to hold, since two processes share the file:

View file

@ -16,7 +16,7 @@ That is the whole intent of the first slice. The point is to make the _unit_
real — service user, runtime and state directories, socket, nginx real — service user, runtime and state directories, socket, nginx
reachability — so the swarm-level surfaces that follow have somewhere to land. reachability — so the swarm-level surfaces that follow have somewhere to land.
Inventing those surfaces before they are agreed would bake in a shape nobody Inventing those surfaces before they are agreed would bake in a shape nobody
chose. See #3066 and the `hyperhive.swarm` consolidation epic. chose. See `$HIVE_FORGE_URL/hyperhive/hyperhive/issues/3066` and the `hyperhive.swarm` consolidation epic.
## Why a unix socket, not a port ## Why a unix socket, not a port

View file

@ -32,7 +32,8 @@ There used to be: a private `users.json` here, canonical, with `users.yml`
rendered from it, while `swarm-authelia-bridge` kept its own pair against rendered from it, while `swarm-authelia-bridge` kept its own pair against
the _same_ physical file. Two canonical stores for one file is a seam, and the _same_ physical file. Two canonical stores for one file is a seam, and
it bit — a writer whose own JSON was missing could not tell "nothing here it bit — a writer whose own JSON was missing could not tell "nothing here
yet" from "someone else's users", and refused to write (#3422). yet" from "someone else's users", and refused to write
(`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/3422`).
The argument for the split was that it let this crate work without a YAML The argument for the split was that it let this crate work without a YAML
parser. It didn't: the JSON was read back on every run, so the round-trip parser. It didn't: the JSON was read back on every run, so the round-trip