Deployment adapters
Push selected published values to Forgejo or GitHub Actions without reading provider values back.
If a job can fetch values at runtime, use workload federation. Use a deployment adapter only for static values that must exist before the first fetch.
Both compiled-in adapters are one-way and value-blind. They write selected values from one Hikyo environment to a repository, organization, or GitHub repository-environment Actions namespace. Neither adapter links a variable GET or list endpoint. Secret planning reads names only; variable planning uses Hikyo’s ownership ledger. Hikyo therefore cannot detect provider-side value drift. Run a converge to restore the last published Hikyo state.
Requirements
- Forgejo 1.21 or newer.
- A repository target PAT with
write:repository, or an organization target PAT withwrite:organization. Forgejo write scopes also permit GET requests, but Hikyo’s variable-read operations do not exist in the client. - GitHub requires a fine-grained PAT (
github_pat_); classicghp_PATs are refused before provider contact. Grant the fine-grained Secrets and Variables write permissions for the chosen repository or organization. Creating a missing environment additionally requires Administration (write): either pre-create it in GitHub and keep the delivery token narrow, or grant that permission for Hikyo-managed creation. manage-adaptersfor inspection and lifecycle operations. Creating, widening, adopting, changing a prefix, replacing a credential, and manually syncing also require reveal authority and adapter reauthentication for every environment synchronized by that adapter.
The credential is write-only after entry. hikyo adapter create and
hikyo adapter credential set read it from a no-echo terminal by default, or
from --stdin or --value-file. There is no credential-value argument.
Targets and names
An adapter owns one provider origin and credential. Each target binds one Hikyo
environment to one provider destination and an explicit,
non-empty key subset. Organization targets are wide: every repository in that
organization allowed by its all, private, or selected visibility can
consume the organization-level values. Selected repositories are stored and
verified by immutable numeric id. Moving to all, changing between private
and selected, or adding selected ids is a widening ceremony; all to
private/selected and selected-id removal are plain narrowing. That breadth
is a storage-level property, not an override guarantee: a repository-
level secret or variable with the same name shadows the organization-level
value for workflows in that repository.
For GitHub, a repository environment is the recommended multi-environment
shape: map dev, stage, and production to separate GitHub environments with no
prefix. A missing environment is created during the reauthenticated target-add
flow with an empty settings body, then its repository id and environment id are
pinned. Later absence or either id changing is refused; Hikyo never redirects
the write to repository scope. Free-plan private repositories without
environments must be reconfigured explicitly to repository scope with prefixes.
Workflow output for an environment target includes the exact environment:
selection before the canonical-name mapping.
Effective provider names are prefix + canonical key name. The optional
prefix applies to every key and to the MANAGED_BY_HIKYO sentinels. Effective
name sets for targets sharing a destination must be disjoint. Use distinct
prefixes when multiple environments feed one repository.
Applications still consume canonical names. Generate the required workflow mapping without exposing values:
hikyo adapter target show tgt_123 --format workflowFor example, a PROD_ target produces mappings from DATABASE_URL to
${{ secrets.PROD_DATABASE_URL }}. Local execution, rendered env files, and
the Kubernetes operator continue to use DATABASE_URL.
Lifecycle
Create validates the provider connection and destination identity before
storing the adapter credential and first target. A missing GitHub environment
is the configure flow’s first provider write: a durable generation-1 effect
fence and correlated adapter.push_intent exist before its settings-free PUT,
and adapter.push_outcome closes that fence.
GitHub storage caps are 100 secrets/500 variables per repository, 1,000/1,000 per organization, and 100/100 per environment. Plans warn exactly from the complete secret-name list and report variables as a lower bound because Hikyo cannot safely read provider variables. Organization workflows expose only the first 100 secrets alphabetically; combined organization and repository variables over 256 KB can also truncate at consumption time. Plan/sync warnings name both limits.
Published environment changes automatically queue a newest-wins converge for
intersecting targets under their recorded authority. adapter plan reports
names and dispositions only. Existing provider names are never captured
silently: adoption requires an exact conflict artifact, generation,
destination, and (surface, name) pairs. If multiple exact-match artifacts are
eligible, pass --artifact explicitly.
hikyo adapter show includes every target’s sync status, converged revision,
failed names, and pending conflict artifacts. Its table view prints the pending
surface:name pairs; JSON retains the full artifact id, generation,
destination, entries, and creation time required for an exact adoption.
Origin and destination changes are asynchronous scrub-before-switch moves.
They block new pushes, scrub the old route, activate and test the pending route,
then converge it. The update response contains a move id; API clients poll
GET /api/v1/orgs/{org}/projects/{project}/adapter-moves/{move} for the state,
per-target scrub/activation/converge jobs, pending route, and orphaned names.
Credentials never appear in a move response. --keep-remote skips the scrub,
releases custody immediately, and enumerates the resulting orphaned names.
When an old credential is dead, the move uses that same orphan-and-release rule
before proceeding.
Transient activation failures retry. An invalid pending credential or a
collision on the new route leaves the move in attention_required; Hikyo does
not silently roll back. PATCHing the concrete move replaces either its pending
origin plus write-only credential or its complete pending target route, then
resumes activation. DELETEing that move atomically cancels it, restores the old
route, and queues old-route reconvergence. The CLI requires the move id through
--move for either recovery path, so pending and live credential replacement
cannot be confused. The browser starts an origin move from the adapter panel
(Change origin), carries the move id in the page URL, polls the move in a
Route move pane until the server settles it, and offers exactly the two exits
of an attention-required move: Resume with a new credential and Cancel move.
Target environments are immutable: cross-environment movement is remove plus
add, so authority and custody are never silently rebound.
Adapter reauthentication is distinct from value reveal. Its signed windows
bind purpose adapter, the exact operation, and the complete environment set.
One TOTP proof covers environments whose effective window is nonzero; each
effective-zero environment requires its own WebAuthn proof. The CLI handoff is
state- and PKCE-bound, has a single-use authorization code, and rotates the CLI
bearer. The CLI binds an ephemeral 127.0.0.1 callback before opening
/reauth/cli?transaction=<opaque-state>. The browser reads only display policy,
performs the required ceremonies, and returns only code plus the same state
to that exact callback. Only redemption returns the bearer; the CLI replaces
its mode-0600 local artifact without printing or logging it.
Removing a target or adapter scrubs ledger-owned names by default, with
sentinels last. --keep-remote releases custody instead and loudly lists the
orphaned names. Remove targets while the PAT still authenticates. If a dead or
revoked credential prevents scrub, Hikyo completes local teardown but reports
the remote names as orphaned.
Multiple targets: selection, health, and control
One published revision fans out to every target configured for its environment, across every adapter kind, through one immutable job per target pinned to that revision, the target’s configuration generation, and the adapter’s recorded authority. Targets never share a job: a failure, a pause, or a conflict at one destination cannot block, roll back, or falsely mark another target successful.
Selecting keys. Membership is always an explicit set of immutable key ids.
--keys names ids; --names names keys exactly; --include and --exclude
are bounded glob patterns over key names (DB_*, *_TEST); --classification
keeps only secret or config keys. Patterns and classification are resolved
to ids when the target is saved and are not stored: a key created later that
would have matched is never added on its own. --exclude narrows only what
the patterns selected, never an explicit id or name. Widening the resolved set
is a routing mutation and carries the full reauthentication ceremony.
hikyo adapter target add --adapter adp_123 --env prod --kind repository \
--owner acme --repo app --prefix PROD_ --include 'DB_*' --exclude '*_TEST' \
--classification secretNaming. The per-target prefix is the one structural name mapping: every
name the target writes is prefix + canonical name, validated whole against
the provider grammar and collision-checked case-insensitively across the
target and against every other target on the same destination. Values are
never transformed. There is no per-key rename table by design; see the
deployment-adapter ADR.
Health. adapter target list and the target detail report one of:
| Status | Meaning |
|---|---|
never | No converge has run. |
pending | A converge is queued and not yet claimed. |
converging | A worker holds the job. |
converged | The destination holds converged_revision. |
degraded | The last attempt failed but an earlier revision converged; the destination still holds it. |
failed | The last attempt failed and nothing has converged yet. |
paused | An operator paused the target. |
Beside the status: the last successful and last attempted revisions and time,
a bounded error class (auth, network, conflict, provider_limit,
provider_ambiguous, refused; never a provider response body), the retry
time when a retry is queued, and attention when the destination disagrees
with the ownership ledger in a way only an operator can settle (an unowned
name in the way, a destination whose identity moved, orphaned names). Attention
clears on the next successful converge.
Pause and resume. hikyo adapter target pause <target> stops every push
without deleting owned destination state: the active job is superseded, a
worker still inside it is fenced, publishes skip the target, and a manual sync
is refused. Pause narrows what Hikyo does and carries manage-adapters alone.
hikyo adapter target resume <target> runs the sync ceremony, queues one
catch-up converge, and prints the published revision it reaches.
Resync. hikyo adapter sync --target <target> is the idempotent resync:
newest wins, a second request supersedes the first, and neither bypasses
adoption nor prunes beyond the ledger.
Removal keeps the explicit retain-or-prune decision described above. The browser offers the same choice in a dialog with neither option preselected, for one target (Remove) and for the whole adapter (Delete adapter).
Credential custody and probes. The adapter panel replaces the write-only
credential (Replace credential, behind the credential-set ceremony) and
destroys custody (Revoke credential, stating that a later remote scrub may
then be impossible). The target detail runs Plan (names and dispositions
only) and Test connection (provider version, destination identity and
credential expiry; no value, no write), the browser forms of adapter plan
and adapter test.
Operators. hikyo doctor reports the instance-wide counts of targets that
need attention, failed targets, paused targets, and queued jobs, and the same
four label-free gauges appear on /metrics as hikyo_adapter_targets_failed,
hikyo_adapter_targets_paused, hikyo_adapter_targets_attention, and
hikyo_adapter_jobs_queued.
Crash windows. If a worker dies between a push’s INTENT and its OUTCOME,
the next claim of that target settles the effect as OUTCOME unknown,
correlated to the job that opened it; the ownership row stays presumed
written and the retry converges it. On a multi-node installation every worker
competes for jobs under the outbox lease; a worker that loses its lease can no
longer push, mark success, retry, or scrub.
Private origins and GHES
Adapters deny private, loopback, link-local, documentation, and reserved
addresses by default. Operators can add origin-scoped exceptions with
HIKYO_ADAPTER_EGRESS_POLICY_FILE; see
server configuration. HTTPS, certificate validation,
DNS rebinding protection, and the no-redirect rule remain mandatory.
GitHub Enterprise Server uses its configured HTTPS API base and inherits the
GitHub contract best-effort; github.com contract pins do not claim every GHES
version behaves identically.