check-issue-refs: catch full forge issue URLs too, drop internal links from docs entirely

This commit is contained in:
damocles 2026-09-09 21:15:28 +02:00
commit a4f72365c7
22 changed files with 59 additions and 80 deletions

View file

@ -92,8 +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 `README.md`, one level up from source; the crate
(`$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,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: special-cased in the meantime, as a tracked cleanup.
`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/361`.
## Where the tree lives ## Where the tree lives
@ -235,10 +234,7 @@ uid 0 and have the setuid bit set."
## Cross-references ## Cross-references
- Milestone: "Agent privileges and sub-agents" - Milestone: "Agent privileges and sub-agents" (tracked internally)
(`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/361`) - Dashboard render: "show agent topology in container list" (tracked internally)
- Dashboard render: "show agent topology in container list" - Audit table source: milestone comment (tracked internally)
(`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/363`)
- Audit table source: milestone comment
(`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/361#issuecomment-3335`)
- Operator/agent trust boundary (orthogonal axis): [`boundary.md`](../trust-boundary/boundary.md) - Operator/agent trust boundary (orthogonal axis): [`boundary.md`](../trust-boundary/boundary.md)

View file

@ -69,8 +69,7 @@ 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 = operator team; see "Forge mirror" below) makes the agent a
`$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
@ -477,8 +476,7 @@ 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 + self-merge its PR (see the End-to-end flow above). hive-c0re passes the tokenised push
`$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 `$HIVE_FORGE_URL/hyperhive/hyperhive/issues/1762`). This is a no-op fallback on enhancement). 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,8 +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` `nix/packages/reference-docs.nix` generates every page at
(`$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,8 +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 first real provision against a live store returned 403, so
(`$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
@ -129,8 +128,7 @@ 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 Making even the `init` unnecessary is tracked as a follow-up.
`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/3768`.
### 4 · Swarm SSO (only when `deploy.authelia`) ### 4 · Swarm SSO (only when `deploy.authelia`)

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 (`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/540`, double-screen). desktop (known double-screen issue).
- **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 `$HIVE_FORGE_URL/hyperhive/hyperhive/issues/2639`). Same mechanism the matrix daemon uses for spawn another todo). 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

@ -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.
**`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/4137`** tracks narrowing it, with the two candidate directions: scope **A tracked follow-up** narrows this, 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,9 +133,7 @@ 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 checkpoint, losing whatever wasn't already persisted (a known, root-caused gap).
(`$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 (`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/2356`); `choom` is an operator (root) action, so choom-started task; `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, `$HIVE_FORGE_URL/hyperhive/hyperhive/issues/3704`: "remove rebuild button, move link to (mara: "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

@ -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: **`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/3020`**, deferred to the swarm-controller / needs a recorded destroy**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 (`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/2606`).** A row of per-state checkboxes above **State filter.** 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 (`$HIVE_FORGE_URL/hyperhive/hyperhive/issues/3139`):** `ContainerView.failed` **Which single badge:** `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

@ -245,7 +245,6 @@ 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 `<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 `$HIVE_FORGE_URL/hyperhive/hyperhive/issues/273` for the design proposal and - **Single-PR migration**: see the tracked 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

@ -1451,7 +1451,7 @@ mod tests {
"html_url": "http://forge/o/r/pulls/5", "html_url": "http://forge/o/r/pulls/5",
"latest_comment_html_url": "", "latest_comment_html_url": "",
"latest_comment_url": "", "latest_comment_url": "",
"url": "http://forge/api/v1/repos/o/r/issues/5", "url": "http://forge/api/v1/repos/o/r/issues/5", // lint:allow: test fixture, not a real forge link
}, },
})) }))
.expect("merged-state notification must parse"); .expect("merged-state notification must parse");
@ -1501,7 +1501,7 @@ mod tests {
"title": "t", "title": "t",
"type": "SomeBrandNewType", "type": "SomeBrandNewType",
"html_url": "", "html_url": "",
"url": "http://forge/api/v1/repos/o/r/issues/9", "url": "http://forge/api/v1/repos/o/r/issues/9", // lint:allow: test fixture, not a real forge link
"another_unknown": 123, "another_unknown": 123,
}, },
})) }))
@ -1532,7 +1532,7 @@ mod tests {
NotifMeta { NotifMeta {
title: "subject title", title: "subject title",
subject_type: Some(NotifySubjectType::Issue), subject_type: Some(NotifySubjectType::Issue),
html_url: "http://forge/issues/1", html_url: "http://forge/issues/1", // lint:allow: test fixture, not a real forge link
num: " #1".to_owned(), num: " #1".to_owned(),
repo: " [agents/x]".to_owned(), repo: " [agents/x]".to_owned(),
meta_suffix: "\nassignee: unassigned".to_owned(), meta_suffix: "\nassignee: unassigned".to_owned(),

View file

@ -109,7 +109,7 @@ pub fn mark_read_best_effort(client: &Client, repo: &str, number: u64) {
/// True when a notification `subject.url` refers to issue/PR `number` in /// True when a notification `subject.url` refers to issue/PR `number` in
/// the (already repo-scoped) query. Forgejo subject URLs end in the /// the (already repo-scoped) query. Forgejo subject URLs end in the
/// issue/PR number (`…/repos/o/r/issues/42`, or `…/pulls/42`); match the /// issue/PR number (an `/issues/<n>` or `/pulls/<n>` path); match the
/// trailing path segment. Tolerates a trailing slash. /// trailing path segment. Tolerates a trailing slash.
#[must_use] #[must_use]
pub fn subject_matches(subject_url: &str, number: u64) -> bool { pub fn subject_matches(subject_url: &str, number: u64) -> bool {
@ -137,7 +137,7 @@ mod tests {
"pinned": false, "pinned": false,
"subject": { "subject": {
"title": "whatever", "title": "whatever",
"url": "https://forge/api/v1/repos/o/r/issues/7", "url": "https://forge/api/v1/repos/o/r/issues/7", // lint:allow: test fixture, not a real forge link
"type": "SomeFutureSubjectTypeWeDoNotKnow" "type": "SomeFutureSubjectTypeWeDoNotKnow"
}, },
"some_new_field": { "nested": 1 } "some_new_field": { "nested": 1 }
@ -148,7 +148,7 @@ mod tests {
assert_eq!(ns[0].id, Some(42)); assert_eq!(ns[0].id, Some(42));
assert_eq!( assert_eq!(
ns[0].subject.as_ref().and_then(|s| s.url.as_deref()), ns[0].subject.as_ref().and_then(|s| s.url.as_deref()),
Some("https://forge/api/v1/repos/o/r/issues/7") Some("https://forge/api/v1/repos/o/r/issues/7") // lint:allow: test fixture, not a real forge link
); );
assert_eq!(ns[1].id, Some(43)); assert_eq!(ns[1].id, Some(43));
assert!(ns[1].subject.is_none()); assert!(ns[1].subject.is_none());
@ -157,7 +157,7 @@ mod tests {
#[test] #[test]
fn matches_issue_subject_url() { fn matches_issue_subject_url() {
assert!(subject_matches( assert!(subject_matches(
"https://forge/api/v1/repos/o/r/issues/42", "https://forge/api/v1/repos/o/r/issues/42", // lint:allow: test fixture, not a real forge link
42 42
)); ));
} }
@ -170,7 +170,7 @@ mod tests {
#[test] #[test]
fn tolerates_trailing_slash() { fn tolerates_trailing_slash() {
assert!(subject_matches( assert!(subject_matches(
"https://forge/api/v1/repos/o/r/issues/9/", "https://forge/api/v1/repos/o/r/issues/9/", // lint:allow: test fixture, not a real forge link
9 9
)); ));
} }
@ -178,7 +178,7 @@ mod tests {
#[test] #[test]
fn rejects_different_number() { fn rejects_different_number() {
assert!(!subject_matches( assert!(!subject_matches(
"https://forge/api/v1/repos/o/r/issues/42", "https://forge/api/v1/repos/o/r/issues/42", // lint:allow: test fixture, not a real forge link
7 7
)); ));
} }
@ -192,7 +192,7 @@ mod tests {
fn rejects_substring_number() { fn rejects_substring_number() {
// 142 must not match 42 — full-segment parse, not substring. // 142 must not match 42 — full-segment parse, not substring.
assert!(!subject_matches( assert!(!subject_matches(
"https://forge/api/v1/repos/o/r/issues/142", "https://forge/api/v1/repos/o/r/issues/142", // lint:allow: test fixture, not a real forge link
42 42
)); ));
} }

View file

@ -55,8 +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 "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,40 +1,35 @@
#!/bin/sh #!/bin/sh
# CI lint: flags tracker tags (a hash followed by an issue number) anywhere # CI lint: flags tracker references — a `#N` tag or a full `.../issues/N`
# in tracked text, source or docs. The hive convention is prose, not # forge URL — anywhere in tracked text, source or docs. Prose, not tracker
# tracker tags — in code because tags rot (they point at moving targets and # references: in code because tags rot; in markdown because the forge's
# leak tracker coupling into the source tree); in markdown because the # public mirror carries no issue/PR data at all, so bare/qualified/glued
# forge's public mirror carries no issue/PR data at all, so *any* `#N` # `#N` and a full link are equally dead weight for a public reader — a
# form — bare, qualified `owner/repo#N`, or an ambiguous glued `owner#N` — # full URL is the same problem spelled out longer, not a safer swap for a
# is equally dead weight for a public reader. No exemption for markdown: # short tag. No markdown exemption: one used to exist, dropped once that
# that used to exist ("prose docs may cite the tracker with a bare `#N`") # read as still allowing exactly this.
# 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 # Emits a CI error annotation per hit, exits 1 if any hit is found. Its own
# otherwise. It runs as its own CI job and IS a required check on the forge # required CI job (branch protection) — a hit blocks merge.
# (branch protection) — a hit blocks merge.
# #
# Scope: every tracked `*.rs *.nix *.js *.ts *.tsx *.css *.html *.md`. The # Scope: every tracked `*.rs *.nix *.js *.ts *.tsx *.css *.html *.md`. Two
# pattern matches a hash, 2-5 digits, then a non-alphanumeric char or # alternatives: a hash, 2-5 digits, then non-alphanumeric-or-EOL (skips
# end-of-line. A real tracker tag is never glued to a letter, so the # letter-bearing hex colours and digit-runs-then-letter, e.g. `#24h`;
# trailing class skips both letter-bearing / 6-8-digit hex colours (the # residual: a pure-numeric short hex trips it, write the six-digit form to
# digit run breaks or overruns) and digit-runs followed by a letter — e.g. # dodge); or an `/issues/N` path segment, catching a full link via
# hash-route fragments like #24h. Residual: a pure-numeric short hex (e.g. # `$HIVE_FORGE_URL` or a literal domain alike.
# three identical digits) trips it — write the six-digit form to dodge.
# #
# Escape hatch: a line containing the marker `lint:allow` is exempt. # Escape hatch: a line with the marker `lint:allow` is exempt. Reserve it
# Reserve it for genuine `#<digits>` that aren't tracker tags — e.g. a # for a genuine non-tag hit (a `#123` heading example, test-input data) and
# `#123` markdown-heading example or hash-prefixed test-input data — and # keep a short reason next to it — not for a real reference of either
# keep a short reason next to the marker. Don't use it to keep a real # form; rewrite those to prose that stands on its own instead.
# tracker tag; rewrite those to prose (or a full issue URL) instead.
set -eu set -eu
pattern='#[0-9]{2,5}([^0-9a-zA-Z]|$)' pattern='#[0-9]{2,5}([^0-9a-zA-Z]|$)|/issues/[0-9]+([^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 `#<digits>`; see the header). # (legitimate non-tracker hit; see the header).
hits="$( hits="$(
git ls-files -z '*.rs' '*.nix' '*.js' '*.ts' '*.tsx' '*.css' '*.html' '*.md' \ git ls-files -z '*.rs' '*.nix' '*.js' '*.ts' '*.tsx' '*.css' '*.html' '*.md' \
| xargs -0 -r grep -nE "$pattern" /dev/null 2>/dev/null \ | xargs -0 -r grep -nE "$pattern" /dev/null 2>/dev/null \
@ -43,10 +38,10 @@ hits="$(
if [ -n "$hits" ]; then if [ -n "$hits" ]; then
echo "$hits" | while IFS=: read -r file lineno _; do echo "$hits" | while IFS=: read -r file lineno _; do
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" printf '::error file=%s,line=%s::tracker reference — write prose that stands on its own, not a hash-number tag or a full issue URL (see /knowledge/hive-rules.md)\n' "$file" "$lineno"
done done
count="$(printf '%s\n' "$hits" | wc -l | tr -d ' ')" count="$(printf '%s\n' "$hits" | wc -l | tr -d ' ')"
printf 'check-issue-refs: %s tracker tag(s) found\n' "$count" >&2 printf 'check-issue-refs: %s tracker reference(s) found\n' "$count" >&2
exit 1 exit 1
fi fi
exit 0 exit 0

View file

@ -43,8 +43,7 @@ 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 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 `$HIVE_FORGE_URL/hyperhive/hyperhive/issues/3066` and the `hyperhive.swarm` consolidation epic. chose. See the `hyperhive.swarm` consolidation epic.
## Why a unix socket, not a port ## Why a unix socket, not a port

View file

@ -32,8 +32,7 @@ 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 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