This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
The Paigasus AI SDK (codename Helikon, after Mt Helicon where Pegasus's hoof struck the Hippocrene spring). A Rust SDK for building AI agents. All crates live under the paigasus-helikon-* namespace.
The full architectural reference lives in Notion: "Crate Reference". Linear project: Paigasus Helikon (issues prefixed SMA-).
cargo build --workspace # all 14 crates
cargo build --workspace --all-features # facade with every optional crate
cargo run -p paigasus-helikon-cli --bin helikon
cargo run -p paigasus-helikon-cli --bin paigasus-helikonTo reproduce every CI gate locally (matches .github/workflows/ci.yml job-for-job):
cargo fmt --all -- --check
cargo clippy --workspace --all-features --all-targets -- -D warnings
cargo test --workspace --all-features
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps
DOC_COVERAGE_THRESHOLD=80 NIGHTLY_CHANNEL=nightly-2026-05-01 \
bash scripts/check-doc-coverage.sh # requires: rustup toolchain install nightly-2026-05-01The full list lives in CONTRIBUTING.md (single source of truth for contributor policies).
15 crates under crates/. The facade paigasus-helikon re-exports paigasus-helikon-core unconditionally and the other 12 sibling crates behind Cargo features.
Implementation status (as of 2026-07-06): every crate in the workspace carries a real implementation and publishes to crates.io — the original ten (paigasus-helikon-core, paigasus-helikon, paigasus-helikon-macros, paigasus-helikon-providers-openai, paigasus-helikon-providers-anthropic, paigasus-helikon-providers-bedrock (SMA-329), paigasus-helikon-sessions-sqlite, paigasus-helikon-runtime-tokio (ascended from stub in SMA-346), paigasus-helikon-mcp (SMA-327), and paigasus-helikon-tools (SMA-328)) plus paigasus-helikon-providers-gemini (SMA-449), paigasus-helikon-sessions-postgres/-sessions-redis (SMA-330), paigasus-helikon-runtime-axum (SMA-331), and the last four ascends — paigasus-helikon-runtime-temporal, paigasus-helikon-runtime-agentcore (both SMA-332), paigasus-helikon-evals, and paigasus-helikon-cli (both SMA-333). The workspace was first published to crates.io in SMA-385; zero name-claim stubs remain. paigasus-helikon-cli publishes as a binary crate (0.1.0) — its lib target is internal (missing_docs opted out) and carries no stability guarantee, publishing only so cargo install paigasus-helikon-cli resolves. paigasus-helikon-sessions-testkit is the sole non-published crate: an internal Session conformance test harness kept at 0.0.0 with publish = false by design, not a stub awaiting an ascend.
Workspace inheritance is mandatory: per-crate Cargo.tomls only set name, description, and any crate-specific bits. Everything else (edition, rust-version, authors, license, repository, homepage, keywords, categories) inherits from [workspace.package] in the root Cargo.toml. Don't hardcode these per-crate.
Per-crate version is the one exception, with a two-state lifecycle:
-
Stub state —
version = "0.0.0"+publish = falsein Cargo.toml +release = falseblock inrelease-plz.toml. Every stub was pre-published once to crates.io at0.0.0during SMA-385 to claim the name and satisfy the facade's optional-dep resolver. After that pre-publish, cargo refuses to republish (the per-cratepublish = false); release-plz ignores them entirely (therelease = false). -
Released state — bumped to a real version (≥
0.1.0) after the first real public-API ticket lands. The 4-step ascend recipe:- Bump
version = "0.0.0"→"0.1.0"in the crate'sCargo.toml. - Remove
publish = falsefrom thatCargo.toml. - Remove the crate's
[[package]] … release = falseblock fromrelease-plz.toml. - Land as one
chore(release): SMA-### lift stage-1 gates for <crate>commit on the feature branch alongside the implementation. release-plz handles the first crates.io publish on CI.
The 4-step recipe applies to stubs ascending from
0.0.0. The ten already-released crates (-core, facade,-macros,-providers-openai,-providers-anthropic,-providers-bedrock,-sessions-sqlite,-runtime-tokio,-mcp,-tools) ship through release-plz's normal flow — no manual ritual needed for their future bumps. The historical chain ofchore(release): … escape release-plz 0.0.0 trap …commits in the git log (SMA-317/347/350/372/382) is pre-Stage-1 archaeology and won't recur.Caveat — when the ascending crate uses
paigasus-helikon-coreAPI added in the same PR, bumpcoretoo (a 5th step).cargo publish --verifybuilds the ascending crate's tarball against the registry core (thepathis stripped at publish), so if crates.iocorelacks the new API the publish fails withfailed to verify package tarball, and release-plz's combined job (SMA-351) aborts before its release-PR step — a deadlock, sincecorenever gets its auto bump (a squashedfeat(<ascending-crate>)commit attributes nothing tocore). Fix: in the same PR, also bumppaigasus-helikon-core(patch for additive/non-breaking-behind-#[non_exhaustive], e.g.0.2.0→0.2.1) and its[workspace.dependencies]pin + CHANGELOG. release-plz then publishescorefirst, then the ascending crate verifies against the freshcore(dependency-ordered publish). Diagnosed in SMA-321: PR #45's release failed against the stalecore0.2.0; PR #46 (chore(release)bumpingcoreto0.2.1) cleared it.Second-order caveat — the manual core bump silently defeats
dependencies_update, so the facade drifts.release-plz.tomlsetsdependencies_update = true, which is supposed to cascade: when a sibling's version changes, release-plz bumps the facade's[workspace.dependencies]pin and gives the facade a patch bump. But that cascade only runs when release-plz itself performs the sibling bump. The same-PR manual bump above means the sibling version is already at target when the PR merges, so release-plz just publishes it and never runs the dependent-bump step — the facade (paigasus-helikon) is left untouched and stops tracking. Consequence: the facade stays at its old version with stale published dep reqs (e.g. after SMA-346, facade0.2.0still requestedpaigasus-helikon-runtime-tokio = ^0.0.0, so the new runner-boundary surface was unreachable through the facade'sruntime-tokiofeature). Fix: in any PR that uses the same-PR manual bump, ALSO bump the facade (patch:versionincrates/paigasus-helikon/Cargo.toml+ its[workspace.dependencies]self-pin + CHANGELOG) so it republishes with current sibling reqs. Diagnosed after SMA-346: PRs #48/#49 shippedcore 0.2.3+runtime-tokio 0.1.1but left facade0.2.0; PR #50 (chore(release)bumping facade to0.2.1) cleared it. NB: feature branches must match thebranch-namesruleset (feature/**orhotfix/**); achore/**branch is rejected at push withGH013 … creations being restricted. - Bump
Released vs stub crates (exact versions move every release — read each crate's Cargo.toml for current numbers, don't trust hardcoded values here): every crate publishes normally now. The last stubs ascended via the 4-step recipe below: -runtime-axum (SMA-331), -runtime-temporal and -runtime-agentcore (SMA-332), and -evals alongside paigasus-helikon-cli (SMA-333). paigasus-helikon-cli publishes as a binary crate — its lib target is internal and carries no stability guarantee, existing only so cargo install resolves the version; it is not "never published as a library." The sole exception left in the workspace is paigasus-helikon-sessions-testkit, which stays at 0.0.0 with publish = false + a release = false block in release-plz.toml — an intentional internal test harness, not a stub awaiting an ascend.
Third-party version pins live in [workspace.dependencies] (root). Members reference them via dep.workspace = true. Internal crate paths are also in [workspace.dependencies] so the facade can use workspace = true consistently.
- Feature naming: kebab-case in
[features](runtime-tokio), snake-case inpub usealiases (runtime_tokio). They must stay paired across the facade'sCargo.tomlandsrc/lib.rs. paigasus-helikon-cliusesautobins = falsebecause thepaigasus-helikon(hyphen) binary maps tosrc/bin/paigasus_helikon.rs(underscore — hyphens are illegal in Rust filenames). Removingautobins = falsereintroduces a phantompaigasus_helikonbinary that conflicts with the explicit[[bin]]entry.paigasus-helikon-macrosis a proc-macro crate from day one ([lib] proc-macro = true). Don't convert it to a regular lib even though it currently has no macros.- The
paigasus-helikonfacade lib shares its name with thepaigasus-helikonCLI binary by design (Notion ref's "fully-qualified shim alias"). This produces a non-fatalcargo docfilename-collision warning. Don't "fix" it by renaming either — both names are user-facing API. The accepted future fix isdoc = falseon the CLI binary entry. - License is dual
Apache-2.0 OR MIT(decided 2026-05-20, reversing the 2026-05-16 MIT-only call). BothLICENSE-APACHEandLICENSE-MITlive at the repo root; the workspace metadata islicense = "Apache-2.0 OR MIT". Per Rust ecosystem convention — no Apache-only or MIT-only crates in the workspace. Contributions are accepted under the same dual license by default (the standard inbound-equals-outbound clause is restated inREADME.md). - MSRV is
1.94(workspace-package level; raised from 1.85 in SMA-329 because sqlx 0.9.0 declaresrust-version = "1.94.0"— the pre-existing highest floor in the workspace). If a dep raises MSRV, bumprust-versionto what cargo demands rather than downgrading the dep. - Workspace-wide
missing_docsenforcement lives in rootCargo.toml([workspace.lints.rust] missing_docs = "warn"). Each non-CLI crate opts in with[lints] workspace = true. The CLI overrides locally with[lints.rust] missing_docs = "allow"and does not includeworkspace = true— cargo treats[lints] workspace = trueand an inline[lints.<tool>]table as mutually exclusive. When adding a new crate, copy the opt-in block. When adding a newpub usere-export to the facade, give it a///doc comment or-D warningswill fail the docs job. cargo msrvhas no--workspaceflag. The msrv workflow verifies one representative inheriting crate:cargo msrv --path crates/paigasus-helikon-core verify. Because every member usesrust-version.workspace = true, success on one is success on all. Don't "fix" the workflow by adding--workspace; that's what the first SMA-305 CI run died on.- Nightly is date-pinned (
NIGHTLY_TOOLCHAIN: nightly-2026-05-01at the workflowenv:level inci.yml, threaded into the doc-coverage script asNIGHTLY_CHANNEL). The rustdoc JSON coverage format is-Z unstable-optionsand can shift between nightlies; floatingnightlywould be a CI footgun. Bumping is a one-line follow-up chore, not an emergency. - Bootstrap commits on release infrastructure must use
chore(...)ordocs(...)types, neverfeat/fix. release-plz parses every commit since the last per-crate tag — afeat(workspace): ...commit that touches everyCargo.tomlwould attribute a bump to every crate. The SMA-307 bootstrap PR followed this rule; the same rule applies to any futurerelease-plz.tomlorrelease-plz.ymledits.
- Branch per Linear issue:
feature/<sma-####>-<kebab-title>. The branch name is pre-computed in each Linear ticket'sgitBranchNamefield. - Design artifacts per ticket (
docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md,docs/superpowers/plans/YYYY-MM-DD-<topic>.md) land on the feature branch alongside the implementation — not pre-merged tomain. - Keep the public mdBook (
docs/book/) current — update it in the same PR as any user-facing change. Before opening a PR, check whether the work changes public API, the quickstart/example flow, the crate roster (incl. a stub crate ascending to published), or a documented concept; if so, bring the affecteddocs/book/src/*.mdpage(s) into line on the same branch. The book is published frommainand drifts silently otherwise — it sat as the untouched SMA-311 scaffold (13/17 pages still> **Stub.**) through all of Stage 1 before anyone noticed; SMA-423 is the one-time catch-up, and this rule keeps the backlog from rebuilding. A pure-internal change (refactor, CI, deps, release plumbing) needs no book edit — but make that a conscious call, not a silent skip.mdbook build docs/bookmust stay clean ([output.linkcheck] warning-policy = "error"). - Keep crate
README.mdfiles current — update the affected crate's README in the same PR as any change to its public surface. Each of the ten published crates'README.mdis its crates.io (and docs.rs landing-sidebar) page — no crate sets an explicitreadme, so Cargo uses the defaultREADME.md. Before opening a PR, for every crate the work touches check whether the change affects that crate's public API / usage example, its install or feature story, or its published status (a stub ascending to published, a renamed/added feature flag); if so, bring itscrates/<crate>/README.mdinto line on the same branch — and also the facadecrates/paigasus-helikon/README.mdand the rootREADME.mdwhenever the crate roster or the feature → module map changes. README install snippets deliberately use drift-freecargo add(no hardcoded versions), so a routine version bump alone needs no README edit. Like the mdBook, the READMEs drift silently otherwise — they sat as untouched 3-line SMA-304 stubs (Stub — see SMA-304) through all of Stage 1 while ten crates shipped real implementations; SMA-424 is the one-time catch-up, and this rule keeps the backlog from rebuilding. A pure-internal change (refactor, CI, deps, release plumbing) needs no README edit — but make that a conscious call, not a silent skip. - Commit prefix:
<type>(<scope>): SMA-### <message>(e.g.feat(facade): SMA-304 ...). - PR titles must satisfy two independent rules from
pr-title.yml(amannn/action-semantic-pull-request):- Full Conventional Commits format. The action enforces a valid
type(scope):prefix from the action's configuredtypeslist — independent of the subject regex.SMA-317 add anthropic provider(no prefix) fails;feat(providers-anthropic): SMA-317 add anthropic providerpasses. - Subject must start lowercase after the
SMA-###prefix. ThesubjectPattern: ^([A-Z]{2,4}-\d+ )?[^A-Z].+$rejectsfeat(core): SMA-314 LlmAgent + ...becauseLis uppercase; lead the subject with a lowercase verb (add,wire,pin,promote,implement,fix). Per-commit Conventional Commit titles on the feature branch don't trip either rule — only the PR title (which becomes the squashedmaincommit) is gated.
- Full Conventional Commits format. The action enforces a valid
- Linear auto-closes the linked SMA-* issue when its PR merges; no manual status move needed.
- Always implement GitHub Actions against the latest stable major. Before adding or updating any
uses:line in.github/workflows/, resolve the latest release of the action and pin to its commit SHA (never a moving@vNtag). Use:Do not use a plan-time version pin if a newer major has shipped between plan-writing and implementation — bump immediately, then let Dependabot'sgh api repos/<owner>/<repo>/releases/latest | jq -r '.tag_name' gh api repos/<owner>/<repo>/git/ref/tags/<tag> | jq -r '.object.sha' # if .object.type == "tag" (annotated), dereference: # gh api repos/<owner>/<repo>/git/tags/<sha> | jq -r '.object.sha'
github-actionsgroup track patch/minor updates from there. The above-the-fold human-readable version stays as a# action vX.Y.Zcomment so the SHA is auditable. Note the corollary: because that group is configured for patch + minor only, a major bump never arrives on its own and must be swept by hand — which is howactions/checkoutsat on v6.0.2 until SMA-486. dtolnay/rust-toolchainneeds its bump direction checked, not assumed. It does not ship conventional releases — it publishes rolling branches (stable,nightly,master,1.0…1.14) and av1tag that is re-pointed at intervals. As of 2026-08-18releases/latest, thev1tag,master's head, and this repo's pin are all6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772, so a Dependabot bump here is legitimate and was accepted in #201. That has not always been true: through SMA-486v1sat abandoned on a 2025-08-23 commit 11 commits behind the pin, and running the "bump to the latest release" recipe would have proposed an ~11-month downgrade. So do not treat this action as blanket-exempt or as blanket-safe — before accepting any bump, confirm the direction:Independently of all that, one rule is permanent: every site pins a SHA and must pair it with an explicitgh api repos/dtolnay/rust-toolchain/compare/<current-pin>...<proposed-sha> \ --jq '{status, ahead_by, behind_by}' # must be ahead_by > 0, behind_by == 0
with: toolchain: …. The ref name is the toolchain selector, so a SHA ref carries no toolchain — the input is mandatory, not stylistic. The workflow comments point here.- After a PR merges to
main, release-plz opens/updates achore: releasePR (authored by the paigasusbot App) carrying the version bumps + CHANGELOG; in the normal flow merging that PR is what publishes to crates.io (the merged feature PR left versions matching the registry, so its ownmainpush publishes nothing). The exception is a PR that bumps its own version — the stub-ascend ritual — which publishes on its own merge with no separate release PR. Check the release PR after every merge and watch its CI — its release-PRcargo updatecan pull a fresh advisory that reddensaudit/denyon the bot PR only (independent ofmain); fix with achore(deps)pin and release-plz regenerates the PR clean.
.github/workflows/ci.yml runs eight jobs on every PR (the commits job is PR-only; the other seven also run on push to main): fmt, clippy, test (matrix: {ubuntu, macos, windows} × {stable, 1.94}, fail-fast: false), build-no-default-features (SMA-452: cargo build --no-default-features for both paigasus-helikon-runtime-axum and paigasus-helikon-runtime-actix, catching openapi-feature-gating regressions, plus a cargo tree assertion that axum does not leak into the runtime-actix feature graph), docs (with RUSTDOCFLAGS=-D warnings), doc-coverage (nightly rustdoc --show-coverage, aggregated by scripts/check-doc-coverage.sh, gated at DOC_COVERAGE_THRESHOLD — default 80%), commits (SMA-335: convco check against the PR's commit range, gated by if: github.event_name == 'pull_request'), and sessions-it (SMA-330: Postgres/Redis session integration suite, path-filtered, on both PR and push to main). The paigasus-helikon-cli crate is excluded from both the missing_docs lint and the coverage aggregator until its public API stabilizes.
.github/workflows/integration.yml (SMA-457) runs two signal-only jobs — temporal-it and agentcore-image — on PR, push to main, a nightly cron at 05:00 UTC, and workflow_dispatch. Deliberately not in ci.yml: temporal-it is expected to flake while it earns promotion (its crash-resume test aborts a real worker against wall-clock activity timeouts), and a failing job makes its whole workflow run conclude failure whether or not it is required — so keeping these out of ci.yml is what stops "ci is red on main" from becoming meaningless. Neither job appears in main-protection-checks.json; "signal-only" means not listed as required, never continue-on-error — that would report green unconditionally and remove the signal rather than weaken it.
Both use step-level if: guards, not a job-level one, for the same reason sessions-it does: a skipped job reports no status at all, which blocks every PR the moment the context is promoted to required. dorny/paths-filter needs a diff base that schedule and workflow_dispatch do not provide, so the filter step is itself event-guarded and a decide step collapses "filter matched" and "manually or nightly triggered" into one output. agentcore-image maps schedule to false. Measured cold on the first CI run, both images build and all four gates run in ~4 minutes, so this is not a cost decision — re-measuring two numbers that sit far under their budgets every night simply adds no information. workflow_dispatch still reaches the job.
temporal-it installs a checksum-pinned temporalio/cli tarball (version and SHA-256 as literals in the workflow — a checksum fetched from the same host as the artifact would prove only that the download was not corrupted, never its identity; hand-bumped, Dependabot does not track it), runs temporal server start-dev --headless, and probes readiness with temporal operator namespace describe default rather than operator cluster health — the cluster reports healthy before the default namespace the suite connects to finishes registering, and a namespace-not-found in the first test would look like a real regression. It sets HELIKON_REQUIRE_TEMPORAL=1, which turns gate() in temporal_live.rs from a loud skip into a panic. That is load-bearing, not belt-and-braces: a skipped test passes, and cargo test captures a passing test's output, so without it a job that never reached a server is indistinguishable from a green one (the same reasoning as HELIKON_REQUIRE_SANDBOX). It deliberately does not retry — unlike sessions-it, which is required and retries three times so a flake cannot block a merge. The whole point of the signal-only phase is to measure the flake rate, and a retry loop erases exactly that evidence; the per-run record goes to the job summary. Promotion bar: ≥ 20 executed runs with ≤ 1 flake, or 30 consecutive green nightlies — at which point temporal-it is added to both main-protection-checks.json and CONTRIBUTING.md's required-contexts table, and the retry decision is revisited.
agentcore-image runs on ubuntu-24.04-arm (free for public repos) because the Dockerfile hardcodes --platform linux/arm64 — AgentCore's runtime targets are arm64 microVMs, and qemu-emulating a musl build of aws-lc-rs would take an hour-plus per image. It runs scripts/agentcore-image-check.sh with AGENTCORE_COLD_START_LIMIT_MS=250, because the 50 ms AC was measured on a quiet developer machine and a shared runner is a different measuring instrument; the script prints a loud NOTE: … this is NOT the AC value whenever the effective gate differs from the default. The 30 MB size gate is deliberately not overridable — it carries the STOP RULE, and an env knob on it would be precisely the quiet relaxation that rule exists to prevent. The Dockerfile's builder RUN uses BuildKit cache mounts so the second image reuses the first's compiled dependencies (~40–50% off the second build; no help to the first, and no persistence across runs — every job gets a fresh runner). The cp out of target/ must stay inside that same RUN: a cache mount is not part of the image filesystem, so splitting it would silently produce an image with no binary in it.
protoc comes from .github/actions/setup-protoc, a repo-local composite action, not from a third-party one (SMA-458). It installs protoc 35.1, pinned exactly and verified against a per-platform SHA-256 before extraction, at all nine sites that compile the workspace (ci.yml ×6, msrv.yml, release-plz.yml, integration.yml). It replaced arduino/setup-protoc, whose version input defaults to 23.x, not to latest — the action's README claims otherwise and is wrong, and CI had therefore been running 23.4 since SMA-332. SMA-458 was consequently a deliberate 12-major upgrade as well as a pin, not the no-op its one-line ticket framing implied. install.sh does download → verify → extract → export; that order is load-bearing, so an unverified archive never reaches an executable location. It exports PROTOC and PROTOC_INCLUDE via $GITHUB_ENV as well as prepending to $GITHUB_PATH, because prost-build resolves PROTOC before falling back to a PATH lookup — that makes the install authoritative regardless of PATH ordering, and moots the well-known-type include/ tree having to sit beside the binary. verify.sh must stay its own step: $GITHUB_PATH/$GITHUB_ENV writes do not affect the step that makes them, so an assertion folded back into install.sh would validate a local export PATH= rather than the mechanism cargo sees, and would be structurally blind to the propagation failure it exists to catch. Only Linux-X64, macOS-ARM64 and Windows-X64 are supported; anything else exits non-zero naming the file to edit. linux-aarch_64 is deliberately absent even though agentcore-image runs on ubuntu-24.04-arm (it has no protoc step) — an unexercised digest is an unexercised code path, and a wrong one reads as tampering rather than as a typo.
Nothing tracks the protoc pin — bumping it is a human act with no prompt. Dependabot follows action SHAs, and after SMA-458 there is no third-party action here for it to follow at all. It sits alongside the repo's other hand-bumped pins: TEMPORAL_CLI_VERSION/TEMPORAL_CLI_SHA256 in integration.yml and NIGHTLY_TOOLCHAIN in ci.yml. Bump runbook: edit PROTOC_VERSION and all three digests in .github/actions/setup-protoc/install.sh and EXPECTED_VERSION in verify.sh, then run bash .github/actions/setup-protoc/selftest.sh — it re-downloads every published asset and fails if any pinned digest disagrees, and also exercises the tampered-digest and unsupported-platform paths. actionlint lints .github/workflows/*.yml and not action.yml, so shellcheck on the three scripts is the only lint coverage the install logic has. A checksum mismatch is not a signal to update the digest — the causes are, in order, a truncated download, an upstream re-tag, and tampering; verify upstream independently first. The accepted cost of pinning is that it does not self-heal: if protobuf removes or replaces the v35.1 assets, every required job and release-plz go red until someone bumps the pin.
.github/workflows/pr-title.yml (SMA-335) runs amannn/action-semantic-pull-request on pull_request_target to gate the PR title — the squashed commit on main. Permissions are minimal (pull-requests: read, statuses: write); no actions/checkout step under pull_request_target keeps PR-controlled code out of the runner. Concurrency keys on github.event.pull_request.number because pull_request_target sets github.ref to the base ref and keying on it would cross-cancel different PRs. Dependabot PRs are exempt from the title check via ignoreLabels: [area:deps] — their auto-generated Bump … titles capitalize the subject and can't be reconfigured, so they'd otherwise block every dependency PR; the ignore label makes the check pass for them (not skip-and-block, which would leave the required context unreported and still block). Don't remove it.
.github/workflows/msrv.yml runs cargo msrv --path crates/paigasus-helikon-core verify as a non-required signal that the declared MSRV is truthful.
The required-status-check contexts gated on main are (bare job names, as posted by the GitHub Actions app): fmt, clippy, test (ubuntu-latest, stable), test (macos-latest, stable), docs, doc-coverage, book-build, commits, pr-title, audit, deny, sessions-it, build-no-default-features. The macOS job is required because it is the only gate that compiles and runs the Seatbelt backend; sessions-it because it is the only gate that runs the live Postgres/Redis session backends; build-no-default-features because it is the only gate that compiles runtime-axum and runtime-actix with default features off and asserts axum does not leak into the runtime-actix feature graph. The canonical declaration is .github/rulesets/main-protection-checks.json (see CONTRIBUTING.md → "Repo configuration"). Other matrix variants (test (windows-latest, …), test (…, 1.94)) run as signals only. Concurrency cancels in-flight PR runs but lets main pushes complete. ci.yml declares contents: read plus pull-requests: read — the latter for sessions-it's dorny/paths-filter, which calls pulls.listFiles on PR events (SMA-487); the workflow comment carries the rationale and the reason it must not be minimised away.
Supply-chain workflows (.github/workflows/audit.yml, deny.yml, sbom.yml) are separate from ci.yml because they have independent triggers and failure semantics. Required status checks added in SMA-306: audit, deny (declared in .github/rulesets/main-protection-checks.json alongside the CI gates). Both audit.yml and deny.yml run on push to main, PRs, a daily cron, and workflow_dispatch — the cron and the manual trigger were aligned in SMA-479 so that main is re-evaluated daily at exactly PR severity.
The two jobs in audit.yml have deliberately different roles, and only one of them is a verdict. The audit job runs cargo audit --deny warnings on every event — it is the same job definition that gates PRs, un-gated in SMA-479 precisely so the daily and PR severities cannot drift apart. Do not re-add an event filter to it, and do not copy its command into a second step somewhere: one command, in one place, is the whole point. The scheduled-audit job runs rustsec/audit-check for its auto-issue-filing behaviour (the only place in the repo where a wrapper action is preferred over direct tool invocation) — and its green status means nothing at any severity. The action routes schedule events to a reportIssues() code path that files issues and returns without ever failing, including for critical vulnerabilities; it also files nothing at all for yanked crates, and never re-files an advisory whose issue has been closed. Read the run conclusion, never scheduled-audit's job status. Correspondingly, do not widen its if: to include workflow_dispatch — a non-schedule event routes the action to reportCheck(), which needs checks: write that the job does not grant, and 403s.
Both workflows key their concurrency group on github.event_name as well as github.ref. This is load-bearing, not decoration: schedule, workflow_dispatch, and push to main all resolve github.ref to refs/heads/main, so a shared group with cancel-in-progress: false lets a queued cron run sit pending until the next merge cancels it — silently discarding the day's only strict evaluation of main. Do not simplify the key back.
Reading the daily signal: green means the strict audit job passed; red means some job in the workflow failed and needs triage; absent or cancelled means unverified. Red is not a synonym for "advisory present" — the run also goes red when the advisory-DB or crates.io fetch hits a network failure, and when scheduled-audit itself fails (e.g. the GitHub API rejecting an issue write) even though audit passed. So check which job failed before concluding anything, then reproduce with cargo audit --deny warnings on a clean checkout. Scheduled runs are also best-effort — GitHub can delay or drop them under load and disables them entirely after 60 days of repository inactivity — so a missing row is not a passing row, and no upper bound on staleness can actually be guaranteed.
To read the verdict against a commit, use the Checks API, not the legacy commit-status API: gh api repos/SMK1085/paigasus-helikon/commits/main/check-runs --jq '.check_runs[] | select(.name=="audit" or .name=="deny") | {name, status, conclusion}'. GitHub Actions publishes check runs; /commits/{ref}/status returns only legacy statuses and on this repo reports just CodeRabbit — so it renders a confident state: success that contains no audit verdict whatsoever. Reading it would reproduce exactly the bug this section exists to prevent. The deny job additionally runs scripts/check-advisory-ignore-sync.sh, which asserts that the [advisories].ignore lists in .cargo/audit.toml and deny.toml have not drifted apart — they are policy-mirrored, and both are now evaluated daily against the same database.
The SBOM workflow invokes cargo cyclonedx --manifest-path crates/paigasus-helikon/Cargo.toml --format json --spec-version 1.5 --all-features. cargo-cyclonedx 0.5.x has no -p flag (must target via --manifest-path) and defaults to --spec-version 1.3, so 1.5 is pinned explicitly. With --all-features the facade's dep graph equals the workspace's dep graph, so one SBOM covers everything. The workflow's find crates/paigasus-helikon -maxdepth 1 -name '*.cdx.json' picks the facade's SBOM specifically — cargo-cyclonedx 0.5 walks the workspace and emits one SBOM under each member directory regardless of which member you point at, so scoping the find pattern matters.
deny.toml declares version = 2 under both [advisories] and [licenses] — v1 fields (vulnerability, unmaintained, unsound, copyleft, etc.) are removed in modern cargo-deny and adding them will fail with a schema error. The license allowlist includes Unicode-3.0 in addition to the ticket-prescribed Unicode-DFS-2016 because unicode-ident ≥ 1.0.13 (pulled transitively by serde_derive) relicensed in 2024. cargo-deny's advisory DB lives at ~/.cargo/advisory-dbs (plural) per deny.toml's db-path; cargo-audit's DB is at ~/.cargo/advisory-db (singular) — each tool caches its own, and the CI cache directories are scoped per-workflow.
Dependabot is configured for cargo + github-actions ecosystems, weekly Monday 06:00 UTC (aligned with the daily audit cron), with patch + minor updates grouped into one PR per ecosystem.
The microvm/forkd live-KVM path is not validated locally or in GitHub CI — the dev host is arm64 macOS (no /dev/kvm) and GitHub runners have none. Validate it on a GCP nested-virtualization VM (Ubuntu 24.04 — the forkd binaries need glibc ≥ 2.38; Intel n2) per docs/runbooks/forkd-live-validation.md. The tests/forkd_live.rs tests are env-gated (FORKD_URL / FORKD_TOKEN / FORKD_SNAPSHOT) and loud-skip when no controller is configured, so cargo test stays green without one.
Hooks are managed via cargo-husky (user-hooks mode) and live in .cargo-husky/hooks/. They're installed into .git/hooks/ on the next dev-dep realization of paigasus-helikon (e.g. cargo test -p paigasus-helikon --no-run). To force re-install after editing a hook: rm -rf target/debug/build/cargo-husky-* && cargo test -p paigasus-helikon --no-run.
That re-install silently does nothing inside a git worktree. cargo-husky walks up from its OUT_DIR looking for a .git directory; in a worktree .git is a file pointing at …/.git/worktrees/<name>, so the search fails and the build script exits having installed nothing. The only trace is a Warning: .git directory was not found in … line in target/debug/build/cargo-husky-*/stderr, which cargo does not surface — the build looks clean and the old hook keeps running. Worktrees share the main checkout's .git/hooks/ (git rev-parse --git-common-dir), so after editing a hook from a worktree, run the re-install from the main checkout. Verify with grep against the installed copy rather than assuming — a stale pre-push survived an entire branch's worth of pushes in SMA-547 this way.
Do not hand-copy a hook onto .git/hooks/<name>. On this machine the Entire CLI owns .git/hooks/pre-push: its wrapper pushes session logs, then chains to the previous hook at .git/hooks/pre-push.pre-entire, which is where the cargo-husky hook actually lives. Overwriting pre-push silently destroys the Entire integration while still appearing to work, because the cargo-husky body runs either way. Check for a .pre-entire sibling first and install into that slot, preserving cargo-husky's three-line # This hook was set by cargo-husky banner. Any hook manager that chains this way (Entire, lefthook, pre-commit) has the same shape.
commit-msg— runsconvco check --from-stdin(enforces the.versionrcallowlist).pre-commit— intentional no-op (exit 0). The file exists to claim the slot so future cargo-husky upgrades don't fill it in with surprise behavior.pre-push— runscargo fmt --all -- --check,cargo clippy --workspace --all-features --all-targets -- -D warnings, andconvco check <merge-base>..HEAD. Catches the three fastest CI gates pre-push; deliberately omitscargo testandcargo doc(too slow for every push). Bypass for WIP branches:git push --no-verify. The convco baseline must be a merge-base, not a branch tip (fixed in SMA-547).convco check A..Bonly walks the commits git would list whenAis an ancestor ofB; given a divergedAit silently falls back to the entire history instead — here 220 commits back toInitial commit, three of which predate the.versionrcscope allowlist and can never pass. The hook previously fedorigin/main's tip, which is diverged for any branch whose first push happens aftermainmoved ahead of its branch point, so it rejected correct branches with failures the author did not write.git merge-baseis an ancestor by construction; don't "simplify" it back to the tip.
.gitattributes pins crates/paigasus-helikon-providers-anthropic/tests/fixtures/*.txt to text eol=lf. The streaming tests include_str! the SSE fixtures and split them on literal \n delimiters; without this, Windows checkouts produce CRLF bytes and the literal-string splits return one part instead of two. When adding wire-format fixtures elsewhere that the test code parses byte-level, extend the rule.
Committed (workspace contains a binary).