Explicit project GitHub routing
Managed PATHs contain the real gh, not a Cacophony shim.
Use caco pr or the explicit shared helper for provider operations; agents and scripts must not call bare gh.
Project routing is transport selection, not new repository or mutation authority.
Inspect without credentials
caco pr gh-env --project example
caco pr gh-env --project example --json
The helper prints only the API hostname, gh account login and host-qualified repository. It makes no provider request, reads no credentials and does not change the global active account. Use the target project for a friend repository; the parent agent's account is not a fallback. This command is inspection-only, not a shell credential export.
Run operator scripts with the shared helper
From a compatible reviewed Cacophony checkout:
scripts/pinned-gh.sh --project example -- pr list
scripts/pinned-gh.sh --project example -- api user
python3 scripts/pinned_gh.py --project example -- api user
The shell entrypoint and Python implementation resolve the route with caco pr gh-env.
--config PATH selects an explicit configuration source.
The route is read with $CACO_BIN first, then with the installed caco on PATH when that is a different binary, so an agent whose CACO_BIN still names an older store path keeps working; the fallback is reported on stderr.
A failed route names each caco tried, its version and its own error output.
Missing/incompatible caco, invalid routing metadata, missing accounts and failed/empty/timed-out lookup all refuse before the requested command; none select an active account or borrow an ambient token.
The underlying native lookup always includes gh auth token --hostname HOST --user USER for the exact configured identity.
Both inherited credential families and debug tracing are cleared before lookup; credentials are passed only in the requested child's environment, never printed or persisted.
API commands receive an explicit --hostname; other gh families receive GH_HOST and the host-qualified GH_REPO.
Foreign-host selectors, global auth changes, token-output commands and replacement Authorization headers refuse.
Literal body, title, jq and field values remain data; real gh owns its option grammar.
For shell consumers, source scripts/pinned-gh.sh and call caco_gh --project P -- <args>.
Python consumers can launch pinned_gh.py with the same arguments.
Neither entrypoint installs a gh executable, changes PATH or modifies stored auth state.
Use the reviewed Cacophony helper path when working from another project's checkout, not a copied credential/config directory.
Existing HTTP release utilities receive credentials without shell token capture:
scripts/pinned-gh.sh --project example --expect-host github.com --exec -- python3 scripts/release-backfill-preflight.py github owner/repo v1.2.3 caco-linux-x86_64
--expect-host validates the configured destination; it does not override it.
--expect-repo OWNER/REPO also requires the configured target repository, before any lookup or command.
--expect-user USER fences a previously observed account, so a changed configuration invalidates a repair preview.
--gh-bin PATH preserves explicit native/synthetic provider transports but applies the same exact host/account lookup to that executable; it is not a shell command or authentication bypass.
Never use --exec to dump an environment or echo credentials.
--exec --nix-source -- nix ... retains Github.com source-fetch authentication in that local Nix client's NIX_CONFIG, not argv, the parent shell, derivations, archives, images or remote builders.
Local lookups are bounded and an outer timeout cleans up its owned keyring-lookup process group.
The public installer bootstrap remains anonymous curl; authenticated checkout updates use this helper, and --public explicitly selects the credential-free path.
GitHub Actions exception
Actions job tokens are not node-global accounts.
Only GITHUB_ACTIONS=true with an injected GH_TOKEN/GITHUB_TOKEN and valid HTTPS GITHUB_SERVER_URL plus GITHUB_REPOSITORY enables the shared helper's job-token path.
That path still pins the destination host and never queries a stored account.
Missing job credentials/metadata refuse rather than falling back to a runner's active login.
Outside Actions, ambient token variables do not choose a project account.
The source guard's reviewed token-bound workflow entrypoints are ci-materialize-selected-ref.sh, ci-cache-contract.sh, ci-resolve-trusted-caco.sh, release-cache-manifest.sh, release-recover-failed-jobs.sh, and release-publish-if-assets-ready.sh under scripts/.
Their workflows supply scoped credentials; they are not general operator entrypoints or a blanket exemption for ci-*.
The shared release publisher can also receive an explicit pinned token through the backfill's --exec boundary; it refuses absent credentials and performs no account lookup.
Legacy multi-context callers
| Caller | Target and credential context |
|---|---|
release-tag.sh | Explicit cacophony route; existing checkout/build/install lifecycle unchanged |
picasso-health-sync-action.sh | mono route, fenced to github.com/gim-home/harry; not the invoking Cacophony action's account |
github-runner-runtime-health.sh | Explicit --project and exact repository; configured host/account also enter the repair fingerprint and receipt |
pi-daemon-acceptance.py | pi-daemon route, fenced to github.com/harryaskham/pi-daemon, retaining exact tag/asset/digest/package proof |
android-ci-live-pr.py | Actions' injected token or native --project cacophony route; provider failure still cannot emit proceed=true |
| Root justfile provider recipes | Explicit cacophony route for secret setting, release download/upload, workflow status and queued-run cancellation |
Android Culture prepare.py | Native paratenic route for the pinned source repository; Actions' explicit source-read credential for cross-repository archive reads |
cs-gh-token.sh, just cluster-gh-login | Retired token-copy entrypoints refuse without looking up or exporting credentials |
Release-tag changes are limited to GitHub routing and retain the parent's public pinning-helper snapshot. The existing checkout, build/artifact paths and local-update behavior are unchanged. Build-tree isolation is separate work in and requires a dry-run proof against the caco-release cron/admin-patch path, shared lock and result-file contract before adoption. This routing change does not authorize a live release or local binary replacement.
Runner local-only checks/preflight need no account lookup.
Provider modes require a compatible caco; the Nix package retains the immutable pinning helper and required runtime dependencies.
GITHUB_RUNNER_GH still selects the native/synthetic executable, but now both its lookup and provider command receive the configured identity.
Existing node/unit/registration/label binding, broker scope, idle observations, preview confirmation and repair receipts remain mandatory.
A changed provider host/account requires a new preview.
Pi-Daemon acceptance can bootstrap in Actions only with an injected token and GITHUB_SERVER_URL=https://github.com plus GITHUB_REPOSITORY=harryaskham/pi-daemon.
An Actions job in another repository is not this bootstrap context; operator use requires the configured Pi-Daemon project instead of a runner's native login.
The Android live-generation guard supports both Actions and operator diagnostics through the shared helper, so it needs no source-guard exception.
Android's companion/android/vendor/culture/prepare.py is a Cacophony-authored consumer materializer, not independently sealed producer SDK bytes.
Its source-transport change leaves source.json, native artifact identity, the separate Rust SDK export/lock and provenance verifier unchanged; no producer source or pin update is needed.
Only Actions may map CULTURE_SOURCE_READ_TOKEN to the scoped child credential, with valid job host/repository metadata; native invocation ignores that variable and all ambient token selectors.
Archive commit/checksum verification, cache refusal, bounded redacted failures and partial-clone sync tripwires are unchanged.
Before caco/configuration exists, bootstrap anonymously with scripts/install.sh --public; there is no host-only/account-only token fallback.
Supply the explicit project/repository/host/account through reviewed enrollment configuration, then use separately authorized first-party encrypted distribution or the configured scoped broker.
See cs-pool bootstrap in the source repository for the input and ACL contract.
Source delivery changes no installed account, credential, workflow, service or deployment.
First-party contract
- The PR target remote comes from
integration.pr_baseand namedremotes, otherwiseproject.remote. project.identityselects the configured identity;identity.usernameis the gh login, falling back only to that identity's map key.identity.hostnamecan specify the API host for a configured SSH alias; otherwise the remote supplies the host, normalizingssh.aliases.- A local Git mirror requires an explicit configured identity hostname/account and exact
integration.cara.repository; local paths never imply a forge identity. - Without an explicit identity, exactly one configured host-matching identity is required; missing/ambiguous identity refuses rather than guessing a repository owner or global account.
- Ambient
GH_*credentials, managedCACO_GH_AUTH_*hints,CACO_GH_COMMAND_OVERRIDE*and checkout-bootstrap functions do not select first-party credentials. - Github.com and
*.ghe.comuseGH_TOKEN; other enterprise hosts useGH_ENTERPRISE_TOKEN, following gh's credential families. - Caco's local lookup defaults to five seconds with a one-second hard-kill grace; script lookup defaults to five seconds with owned-process cleanup.
CACO_GH_TOKEN_LOOKUP_TIMEOUT_SECSaccepts integers 1–60. - Failed, timed-out, empty or whitespace-bearing lookup output never executes the requested command or falls back to another account; errors contain no credential output.
- Caco provider budget attribution occurs after resolution, retaining caller classification and secret-free accounting without nested double-counting.
- Existing repository/host checks, publication leases, queue admission and required CI remain unchanged.
caco-config::gh::ProjectGhRoute owns Caco's typed resolver and command-local launcher.
Non-login shells prevent startup files from replacing the route.
The routing function exists only in the first-party subprocess, never in agent startup or PATH.
Source guard and validation
python3 scripts/check-pinned-gh.py --base origin/main
python3 scripts/tests/pinned-gh-test.py
python3 scripts/tests/legacy-pinned-gh-test.py
python3 tests/pi-daemon-acceptance-fixtures.py
python3 scripts/tests/culture-preparation-test.py
bash tests/github-runner-runtime-health-fixtures.sh
python3 scripts/tests/check-pinned-gh-test.py
cargo test -p caco-config --lib gh:: -- --test-threads=1
python3 scripts/tests/managed-gh-lookup-test.py
The cheap source guard flags newly added direct gh subprocess calls and common shell command/substitution/alias forms, including justfile recipes.
Only the two reviewed pinning implementations and explicit token-bound workflow paths are exempt.
--all audits existing legacy callers as well; an unchanged historical caller is not permission to add another one.
This is a source fence, not an authorization mechanism or complete shell compiler.
Hermetic fixtures exercise exact host/user flags, wrong active/ambient accounts, native and Actions credential families, label inventory, deadlines, process cleanup, private installer behavior and transport failure before heavy builds; no operator auth files or provider are used.
Migration
Normal materialization/environment refresh removes only recognized daemon-generated bin/gh shims with a bounded descriptor reader.
Unknown files are preserved and reported; legacy source-tool custody/receipts remain readable while shim publication refuses.
Source merge does not update an already-running daemon, shell, script checkout or Pi session; compatible deployment and supported owner-controlled refresh remain separate stages.
No credential, account or operator installation migration is performed by the source change.
The former managed shim design is historical; and regressions are carried into typed routing tests rather than restoring a shim allowlist.
This shared transport is used by TUI, web and Android consumers without a new surface-specific API.