Deployment adapters
Push selected published values to Forgejo, GitHub Actions, GitLab, Cloudflare, AWS Secrets Manager, Vault/OpenBao KV v2, or a sealed webhook receiver 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.
For a CI job that creates and tears down its own environments, use the pull-request preview guide.
The deployment adapters are one-way and value-blind. Forgejo and GitHub Actions write to repository, organization, or GitHub environment namespaces; GitLab writes project or group CI/CD variables. Cloudflare writes encrypted Workers secrets or Pages variables. AWS Secrets Manager writes into one account and region, and Vault/OpenBao KV v2 writes one secret per key. CI variable planning uses Hikyo’s ownership ledger, without variable GET or list endpoints. Cloudflare reads settings but decodes only names and types, discarding plaintext fields. AWS and Vault read metadata only; their provider sections explain how metadata detects external changes. CI adapters cannot detect provider-side value drift; run a converge to restore published Hikyo values. The sealed webhook adapter pushes to a generic HTTPS receiver an instance administrator has pinned; it has no read path at all.
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 and explicitly select environment creation in the web form, or pass--create-environmenttohikyo adapter create/hikyo adapter target add. Without consent, Hikyo never creates a missing environment. 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.
GitHub rejects empty variable values. Sync refuses an empty configuration value by key name before any provider request; it does not substitute whitespace or silently omit the variable. Plans remain value-blind. Variables write permission is first proven by the sync sentinel, since connection checks cannot safely read provider variables or mutate them.
The GitHub delivery limits are 48,000 UTF-8 bytes per variable and 47,952 plaintext bytes per secret. These effective limits are pinned by real API boundary probes; they are not 48 KiB. Sync rejects larger values by key name before contacting the provider. It never chunks or re-encodes a value to fit.
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.
GitLab CI/CD variables
The GitLab adapter writes project or group CI/CD variables on gitlab.com or a
self-managed GitLab 16.0 or newer. GitLab’s variable reads return stored
values, so its client has no read or list operation of any kind: every
existence and ownership decision comes from Hikyo’s ledger, and adapter plan
reports keys Hikyo does not yet own as unknown-until-sync. A key already held
by someone else is discovered only when GitLab refuses the create as already
taken. That is recorded as a conflict for explicit adoption, and the existing
variable is never overwritten.
Token. Use a project or group access token with the api scope and the
Maintainer role (Owner for group destinations, which GitLab requires to manage
group variables). A personal access token is refused by default because it can
act as its owner everywhere they have access; creating the adapter with
--allow-personal-token (or the browser checkbox) accepts one and records that
choice in the audit trail. Tokens with sudo or admin_mode are always
refused. An expiry within 14 days shows as a target warning; an expired token
fails the converge as an authentication error.
Destinations. --kind project takes --owner NAMESPACE --repo PROJECT;
--kind group takes --owner GROUP/PATH. Non-default group environment scopes require GitLab Premium or Ultimate; use * on GitLab Free (GitLab API). Both are pinned by numeric id, and a
project or group that moves or is renamed is refused until an operator
reconfigures the target. Each target has one GitLab environment scope,
--scope (default *). The scope is fixed once the target exists; to change
it, remove the target (retain or prune) and add a new one. One project can
receive the same key under different scopes from different Hikyo environments,
which is the usual shape:
hikyo adapter create --provider gitlab --origin https://gitlab.example.com \
--env prod --kind project --owner platform --repo api --scope production \
--protected --include '*'
hikyo adapter target add --adapter adp_123 --env staging --kind project \
--owner platform --repo api --scope staging --include '*'Flags. Secret-classified keys are always written masked. --hidden
creates them masked and hidden (GitLab 17.4 or newer; an older GitLab is
refused by name rather than downgraded). --protected limits every managed
variable to protected branches and tags; removing it later requires the full
reauthentication ceremony because it widens who can read the values. Values are
written raw by default, so a $ is delivered byte-exactly;
--expand-variables opts into GitLab’s variable expansion.
Masking constraints. Hikyo applies a conservative masking policy: a single-line value of at least
8 characters drawn from A-Z a-z 0-9 _ @ : . ~ + / = -. Hikyo checks every
selected secret against that rule before a job is queued and fails by key name
without printing the value. Exclude nonconforming secrets from the target; do not
reclassify sensitive keys as configuration, which is unmasked. GitLab allows a
broader alphabet with expansion disabled, but Hikyo retains this stricter rule.
Hikyo never ships a secret unmasked. Keys must match
[A-Za-z_][A-Za-z0-9_]*, at most 255 characters, and may not start with CI_
or GITLAB_.
Self-managed TLS. --ca-bundle-file adds PEM trust anchors for an internal
CA. --spki-pin pins the server’s base64(sha256(SubjectPublicKeyInfo)), the
same value openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64 prints. The pin is checked after normal
certificate verification, never instead of it. Both are fixed for the
adapter’s lifetime and move with it through an origin change; to change them,
create a new adapter. A self-managed instance on a private address also needs
its CIDR in the adapter egress policy.
The MANAGED_BY_HIKYO sentinel is a single unprotected variable per
destination and scope, written first and removed last. Rate limits honour
Retry-After and RateLimit-Reset.
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.
Cloudflare Workers and Pages
The cloudflare provider synchronizes into two explicit destination kinds that
share one credential and one ownership model:
| Kind | --owner / --account | --repo / --script / --pages-project | --destination-environment |
|---|---|---|---|
workers-script | 32-character account id | Workers script name | empty |
pages-project | 32-character account id | Pages project name | preview or production |
hikyo adapter create --provider cloudflare --env prod --kind workers-script \
--account 0123456789abcdef0123456789abcdef --script api --keys key_1,key_2 --stdin
hikyo adapter target add --adapter <adapter> --env preview --kind pages-project \
--account 0123456789abcdef0123456789abcdef --pages-project site \
--destination-environment preview --keys key_1The origin is always https://api.cloudflare.com; the CLI fills it in.
Preview and production of one Pages project are separate targets with separate
sentinels and ownership ledgers, so a preview sync can never touch production.
Every write is secret_text. Workers secrets are written with
type: secret_text; Pages variables are written into
deployment_configs.<environment>.env_vars with type: secret_text. Keys
classified config are delivered as secrets too. The client has no operation
that writes plain_text, so a destination that cannot keep a value secret is
refused rather than downgraded. The management sentinel MANAGED_BY_HIKYO is
a secret written first and removed last.
Credential. Use a scoped API token sent as Authorization: Bearer. Grant
Workers Scripts: Edit for Workers targets and Cloudflare Pages: Edit for
Pages targets, on exactly one account. Before activation Hikyo verifies the
token is active and unexpired and that it reaches exactly the configured
account. Global API Keys, email/key pairs, and tokens that reach more than one
account are refused. The verified expiry is recorded as the credential expiry,
and every sync re-verifies the token before its first write.
What Hikyo reads. Workers: the script listing (for the script’s immutable
tag) and the script settings, decoded into binding names only. Settings
return plain_text binding values; they are never decoded, logged, or
compared, and the response buffer is cleared. Pages: the project object, decoded into the project id
and the names and types of the selected environment’s variables only. Pages
returns plain_text values in that response; they are never decoded, logged,
or compared, and the response buffer is cleared. Secret values are never
returned by Cloudflare and never requested.
Ownership and conflicts. Both Cloudflare writes are upserts, so Hikyo
re-reads the destination names immediately before each first write of a name
and refuses any name that already exists and is not in the ownership ledger,
including Workers bindings of any type and Pages plain_text variables. Adopt an existing name through the
usual plan-and-adopt flow. A script or project that is renamed, deleted, or
recreated under the same name changes its immutable id; the target then fails
with a destination-identity refusal and needs reconfiguration.
Side effects. Every Workers secret write or delete deploys a new version of
the script. Pages variable changes apply from the next deployment of that
environment; existing deployments keep their values. Target status carries
both as warnings. Writes are one name per request so each effect has its own
INTENT and OUTCOME; Pages PATCH merges per key and a delete sends the name as
null.
Failures. HTTP 429 honours Retry-After (five minutes without one, the
length of Cloudflare’s request window). A 2xx response whose envelope does not
report success: true is recorded as an unknown outcome and replayed as an
update. Hikyo cannot detect a value edited in the Cloudflare dashboard; the
next converge overwrites owned names with the published Hikyo value.
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.
AWS Secrets Manager
The aws-secrets-manager adapter writes selected published values into AWS
Secrets Manager in one account and region. Hikyo stays the source of truth:
the adapter cannot read a value back. Its provider interface has no
GetSecretValue, BatchGetSecretValue, or GetRandomPassword, and a test
fails the build if production code ever names one.
Destinations
Each target picks one destination kind:
| Kind | What lands in AWS |
|---|---|
json-object | One secret named --secret whose value is a JSON object: one member per selected key, named name prefix + key, members sorted, no whitespace. Values are JSON strings, so parsing the document returns the exact published bytes. |
per-key | One secret per selected key, named path prefix + name prefix + key, for example prod/app/DATABASE_URL. The optional path prefix must end in /. |
The destination owner is the 12-digit AWS account id. Hikyo checks it against
STS GetCallerIdentity and against every secret ARN, so a credential or
endpoint that lands in another account or region is refused as a destination
move rather than followed. An optional customer KMS key (--kms-key) is applied
when Hikyo creates a secret; changing it, the mode, the secret name, or the path
is a destination move with the usual scrub-before-switch.
Names are refused, never rewritten: AWS allows A-Z a-z 0-9 / _ + = . @ -,
up to 512 bytes, and Hikyo also refuses a name ending in - plus six
characters, which AWS ARN lookups confuse with the random ARN suffix. A value
must be valid UTF-8 without NUL; per-key values must be non-empty and at most
65536 bytes, and a json-object document must fit the same 65536-byte limit.
Authentication
The adapter credential is a JSON access descriptor that is sealed on save and
never returned. The CLI and the web form assemble it; the API takes it as the
credential string.
| Mode | Uses | Secret material |
|---|---|---|
assume-role | The node’s AWS identity assumes role_arn (optional external_id, session_seconds 900 to 3600). | None |
web-identity | The node’s projected token (AWS_WEB_IDENTITY_TOKEN_FILE, as on EKS with IRSA) assumes role_arn. The descriptor cannot name a token file. | None |
ambient | The node’s default AWS credential chain (environment, ECS task role, EC2 instance role). | None |
static | An access key id and secret access key. | The secret access key |
ambient, assume-role, and web-identity act with the server’s own AWS
identity, so they are refused until the node operator sets
HIKYO_ADAPTER_AWS_WORKLOAD_IDENTITY=allow. Without that opt-in a project
administrator cannot borrow the server’s IAM role. Scope the role’s trust and
permissions accordingly, and prefer assume-role with an external_id per
project.
hikyo adapter create --provider aws-secrets-manager \
--origin https://secretsmanager.eu-west-1.amazonaws.com \
--env prod --kind json-object --owner 123456789012 --secret prod/app/config \
--aws-auth assume-role --aws-role-arn arn:aws:iam::123456789012:role/hikyo-sync \
--aws-external-id hikyo-prod --names DATABASE_URL,API_TOKENFor --aws-auth static, pass --aws-access-key-id; the secret access key is
read like any adapter credential (no-echo prompt, --stdin, or
--value-file). --aws-* flags also apply to hikyo adapter update --origin
and hikyo adapter credential set.
The IAM policy needs, on the target secret ARNs:
secretsmanager:DescribeSecret, CreateSecret, PutSecretValue,
UpdateSecretVersionStage, TagResource, RestoreSecret, and DeleteSecret; plus secretsmanager:ListSecrets
(which AWS does not scope to resources) for per-key planning and connection
tests, sts:GetCallerIdentity, and kms:GenerateDataKey and kms:Decrypt on a
customer KMS key. Do not grant GetSecretValue to the adapter’s role.
Ownership, drift, and deletion
The Hikyo ledger is the only ownership record. On the AWS side each owned
secret carries the tag MANAGED_BY_HIKYO=<target id>, written when the secret
is created (before its first value). Hikyo stages a value with HIKYO_PENDING,
then conditionally moves AWSCURRENT from the exact observed predecessor using
UpdateSecretVersionStage. Only after promotion does it move HIKYO_CURRENT
and record the version in the HIKYO_VERSION tag. AWS automatically makes the
first-ever version current, but conditional promotion still refuses a different
version that became current concurrently.
- Unowned names. A name that exists without Hikyo ownership is a conflict, never a silent overwrite. Adopt it from a plan like any other adapter; Hikyo then tags it before its first write.
- External edits. If
AWSCURRENTpoints at a version Hikyo did not write, the target reports a conflict and needs operator attention; the value is not overwritten. To let Hikyo replace that exact version, tag the secretHIKYO_VERSION=<that version id>(the conflict message names it) and resync. A later edit is not covered by that consent. A concurrent edit between inspection and promotion also refuses the update; the staged Hikyo value does not replace the external current value. - Retries. Each write carries an idempotency token that is identical for every retry of one outbox job, so replaying an ambiguous outcome cannot create a second version. A new job writes a new version.
- Failures. Access denied, KMS denial, and expired credentials are
auth; throttling isprovider_limitand honoursRetry-After; a timeout or 5xx after the request was sent is ambiguous and replays with the same token. Per-key targets report each failed name; the other keys still converge. - Deletion. Prune and teardown delete only ledger-owned names and always schedule deletion with the 30-day recovery window. If the same target needs a pruned name again inside that window, Hikyo restores it; a different target sees the old ownership tag and reports a conflict.
VPC endpoints and emulators
A VPC endpoint origin (or a local emulator) needs region in the descriptor
(--aws-region). Private addresses need an entry in
HIKYO_ADAPTER_EGRESS_POLICY_FILE. HTTPS is always required. The workload
identity modes accept only recognized AWS Secrets Manager regional, FIPS,
China, or VPC endpoint DNS names, with the endpoint region matching the
access descriptor. An egress-policy entry does not authorize an arbitrary
host to receive the node’s credentials. Emulators require static credentials.
Workload identity modes always use AWS’s regional STS endpoint, because STS receives
the node’s own identity material; only static credentials may name a
different STS endpoint (sts_origin, --aws-sts-origin), for an emulator.
Vault/OpenBao KV v2
The vault-kv adapter delivers into a Vault or OpenBao KV version 2 mount.
It is a destination only: it never reads a value back, and it is separate from
hikyo import --from vault, which reads a tree into Hikyo once.
hikyo adapter create --provider vault-kv \
--origin https://vault.example.com:8200[/NAMESPACE] \
--env prod --mount secret --path apps/payments \
--keys ID,ID --stdin < credential.json-
Addressing.
--originnames the server; append/NAMESPACEfor a Vault Enterprise or OpenBao namespace.--mountis the KV v2 mount and--paththe prefix under it (they fill the repository destination’s owner and name, so the API and browser take the same values). Each key becomes one secret atMOUNT/PATH/PREFIXNAMEwith a singlevaluefield.hikyo adapter target show --format workflowprints the path of every key. Secrets and config values share the tree, so names must be unique across both, case-insensitively. Values must be UTF-8 and at most 512 KiB. KV v1 mounts are refused: they have no check-and-set. -
Credential. A bare token, or JSON:
{"method":"approle","role_id":"…","secret_id":"…","mount":"approle"}or{"method":"token","token":"…"}. Either form acceptsca_pem(your CA; hostname is still verified) andspki_sha256(base64 SHA-256 of the server leaf’s public key, checked in addition to chain verification, so pair it withca_pemfor a private CA). AppRole logs in once per delivery attempt, renews at most twice within it, and revokes its token afterwards. A static token is never revoked; its expiry is shown on the adapter. JWT/OIDC login is not supported. -
Least privilege. The adapter needs no data read. A sufficient policy:
path "secret/data/apps/payments/*" { capabilities = ["create", "update"] } path "secret/delete/apps/payments/*" { capabilities = ["update"] } path "secret/metadata/apps/payments/*" { capabilities = ["patch", "read"] } path "sys/internal/ui/mounts/secret" { capabilities = ["read"] } -
Ownership and conflicts. Hikyo writes with check-and-set and records
managed_by_hikyo,hikyo_version, andhikyo_pending_versionin each path’s custom metadata (names and version numbers only). A path that already exists and is not Hikyo’s is a conflict and is not touched until you adopt it by name; adoption never reads the value. A Hikyo path that someone edits outside Hikyo is also a conflict: Hikyo reports it and never overwrites it. If that key is later removed from the selection or the target is removed, Hikyo releases it with a warning instead of deleting the edit. An interrupted delivery replays from metadata, with a residual concurrency limit: if an external write takes exactly the pending version and the attempt stops before releasing custody, replay cannot distinguish that write from Hikyo’s own. A later retry can overwrite it; the external value remains in KV version history. See the Vault adapter ownership protocol before allowing concurrent external writers. -
Retain, prune, destroy. Removing a target with
--keep-remoteleaves every path in place. Without it, and for keys that leave the selection, Hikyo soft-deletes the current version: history and metadata stay, andvault kv undeleterecovers the value until the mount’s own retention drops it. Hikyo never destroys versions or deletes metadata; use Vault tooling for that after removal. -
Versions. Every converge re-delivers every selected value, so each key gains a KV version per converge. Bound history with the mount’s
max_versions. -
Loops. A live
hikyo import --from vaultrefuses to read a tree that an activevault-kvtarget of the same project writes (same host, namespace, mount, and overlapping path), so Hikyo never imports its own output. -
Failures. A sealed or standby server and transport errors retry; a
429honorsRetry-After; a refused token or AppRole login marks the credential for replacement; a re-created or moved mount stops delivery until you reconfigure the target.
For a private Vault address, add an egress exception keyed by
https://HOST:PORT (without the namespace).
Sealed webhook receivers
The sealed-webhook adapter pushes values to your own HTTPS service. Each
value is encrypted to the receiver’s pinned age key and each request is
signed by the Hikyo instance, so the values never appear in a URL, header,
log, audit payload, or response. The receiver answers with a signed
acknowledgement bound to the exact request. The wire contract is
the sealed webhook protocol,
and cmd/hikyo-receiver-example is a reference receiver.
Endpoints are instance configuration. A project cannot point Hikyo at an
arbitrary URL. The instance administrator lists receivers in the file named by
HIKYO_SEALED_WEBHOOK_FILE (see server configuration):
- The receiver operator runs
hikyo-receiver-example keygen -dir DIR, which writes the receiver’s secret keys with mode0600and prints the publicrecipient_keyandack_key. - The receiver operator runs
hikyo-receiver-example fingerprint -origin https://recv.example -recipient age1... -ack-key ed25519:... -generation 1and reads the result to the instance administrator over a separate, trusted channel. - The administrator adds the endpoint with that value as
confirmed_fingerprintand restarts. A missing or different fingerprint refuses startup; a changed host, port, or key changes the fingerprint.
Binding a project. A project administrator creates the adapter with the
activated origin, the receiver namespace as --owner, and a binding
credential the receiver operator issued for that namespace:
hikyo adapter create --provider sealed-webhook --origin https://recv.example \
--env prod --kind organization --owner payments-prod --prefix "" --keys KEY_ID,KEY_IDThe binding credential is read like every adapter credential (no-echo
terminal, --stdin, or --value-file) and travels only inside the
encrypted payload. The connection test sends a signed probe and requires a
valid acknowledgement.
Outcomes. A verified applied or already_applied acknowledgement
advances the target. A missing, oversized, or malformed answer records the
effect as unknown and the retry reuses the same idempotency key, so a
receiver that already applied it answers already_applied. A forged
acknowledgement (wrong key, bad signature, wrong request binding) fails the
job and marks the target as needing attention. A receiver refuses to
overwrite a name it did not receive from this target and answers conflict.
Resolve it by releasing the name on the receiver or renaming with a prefix;
hikyo adapter adopt is refused for sealed-webhook targets because the
protocol has no adopt intent.
Rotation. To rotate the Hikyo signing key, pin the new public key on the
receiver with the old key’s not_after at least a day ahead, replace
signing_key_file, and restart. Rotating a receiver key or moving its host is
a new trust boundary: bump generation and confirm the new fingerprint.
Targets bound under the old fingerprint stop before sending anything. Remove
them (pruning before you change the registry, or keeping the
remote values and asking the receiver operator to release those names), then
add them again. A receiver only lets the target that wrote a name change it.