Acquire, adopt, rotate, verify, remove.
Credential operations are persisted workflows with one writer, provider verification, exact item contracts, receipts, and quarantine state.
Operations#
- acquire asks the registered provider workflow to create a new credential and stores only a provider-verified canonical result.
- adopt takes a value the operator already knows from stdin, verifies a fresh provider login, and commits only after operation_completed.
- rotate changes a managed credential when rollback to the current value is possible; reset is reserved for a lost current value.
- verify authenticates the stored value without changing it; remove revokes provider-side access before local removal.
Rotation is automated after it is requested, not scheduled#
`credential rotate` runs the provider workflow end to end: Weles changes the provider-side credential, Skarbiec verifies the result, commits the new encrypted revision only after provider confirmation, and blocks retries when the provider effect is uncertain.
Skarbiec does not currently include a rotation scheduler, age policy, or `rotate every N days` daemon. An operator or an external scheduled workload must start each rotation. Sync intervals rotate no credentials; they only synchronize ciphertext between bonded vaults.
skarbiec credential rotate <item-id> \
--consumer <consumer> \
--purpose "<why this credential is being rotated>" \
--as <requester> \
--token-file <requester-token-file>When the credential does not exist yet#
A missing credential is an acquisition workflow, not a request for somebody to paste a secret into chat. The caller names the provider, the future consumer, and the purpose. For a generic provider, the provider slug can also be the item id; Skarbiec then creates a managed `api_key` item under that exact name.
The request is recorded before Weles is called. When the lifecycle bridge registers that provider trajectory, Weles owns the provider interaction: it opens the declared signup origin, creates or signs into the provider account, captures the issued credential, and returns a status plus provider-effect evidence. The caller never receives a general vault grant and never carries the new value between systems.
- `operation_completed` commits the provider-verified value to the declared item and field. The consumer still needs its own narrow read grant; acquisition permission does not imply read permission.
- `operation_queued` leaves a resumable pending record. `credential status` follows that exact request instead of starting another account or issuing a second key.
- `needs_human_approval` must include a bounded approval resource. `credential resume` continues the same request after the approved interaction; it does not create a replacement request.
- `needs_configuration`, `unsupported_operation`, or `unsupported_secret` means the provider trajectory is not implemented or cannot satisfy the contract. A writer grant alone cannot turn that refusal into a working acquisition.
- For generic acquisition, `--signup-origin` is an absolute lowercase HTTPS origin with no path, query, userinfo, or fragment. The managed write is refused unless Weles reports the same capture origin.
skarbiec credential acquire <provider-slug> \
--provider <provider-slug> \
--consumer <consumer> \
--purpose "<why this consumer needs the credential>" \
--signup-origin https://<provider-origin> \
--as <requester> \
--token-file <requester-token-file>The item's field is a contract, not a mapping#
A provider contract writes one exact field: password for the Microsoft Account and Microsoft Entra contracts, and api_key for every other provider. Subscription reauthentication signs a human login in, so the item it names carries that account's password. Before a managed item begins any operation, Skarbiec checks that the item already carries that exact name.
An item whose field is named something else is refused by name, and the refusal states both the field the item carries and the field the contract writes. There is no alias and no automatic migration: writing password beside an existing login_password would leave the value the provider now accepts under a key none of that item's readers resolve. Establishing which name is canonical, moving the item and every consumer registration onto it, or leaving the item outside the lifecycle entirely, is an explicit operator decision that Skarbiec refuses every operation until somebody takes.
One command answers why an item is not ready#
Readiness is one question with one answer rather than one refused operation per reason. credential status reports whether the item is lifecycle-eligible, and whenever it is not, every blocking reason at once. The list is never cut short at the first reason found, and an empty list is the only statement that an item is ready.
| Reason | What it means |
|---|---|
legacy_envelope | The item still uses the pre-v2 envelope. Its payload cannot be read at all, so its field cannot be judged until that envelope is gone — migrate it first. |
noncanonical_field | The item's field is not the one its provider contract writes. The detail names both. |
no_directory_contract | No sealed directory block resolves for the item, or its two copies disagree. |
quarantined | The item is frozen until a quarantine resolution settles it. |
Lifecycle state and provider effect#
One credential item is unmanaged, managed, adopting, or quarantined. An unknown stored state is a refusal rather than a guess. Adopting means an operator-supplied candidate is in flight: such an item never reports as externally verified, and the candidate is readable only by the adopt verification path for that exact item, request, field, and presenting consumer.
Every response also carries what the operation did to the password the provider accepts, and that value alone decides whether the same request may be resubmitted.
- A rollback reported as failed or unknown quarantines the item for the same reason. Rollback status is itself one of none, completed, failed, or unknown.
- A staged candidate is never discarded on the way into quarantine, because it may be exactly the value the provider now accepts. Resolution decides its fate: keep it, activate it, or discard it.
- Quarantine resolution demands one exact confirmation sentence and an admin capability on that item, and it returns the item to unmanaged — so knowing the password again is always an explicit act.
| Provider effect | Automatic retry |
|---|---|
none | Allowed. |
changed | Refused until a verify succeeds or a rollback is confirmed. |
unknown | Always refused. The item is quarantined, because nobody knows which password is live. |
Named failure classes#
A lifecycle failure is a code a caller can decide on, not a sentence it has to parse. Each of these is stable and carries a detail naming the exact values an operator must act on.
| Code | What it means |
|---|---|
SKARBIEC_ENDPOINT_UNRESOLVED | No usable canonical endpoint. The forward file must be owner-owned, without group or world write, holding exactly one HTTPS URL or one loopback HTTP URL with an exact port and no path. A fresh installation has none until declare-endpoint writes one. |
SKARBIEC_ENDPOINT_TLS_UNSUPPORTED | The declared canonical endpoint cannot be reached by this binary without a TLS client. |
SERVICE_DIRECTORY_STALE | The canonical service reported a stale service directory rather than serving the call. |
CREDENTIAL_FIELD_CONTRACT_MISMATCH | The managed item carries a field other than the one the provider contract writes. |
DIRECTORY_CONTRACT_DIVERGED | The item's sealed directory identity and its separate seal record name different identities. Every operation is refused until a reseal settles which is correct. |
DIRECTORY_EXPECTATION_MISMATCH | A stated expectation does not match the sealed contract, refused before anything reaches the bridge. |
PROVIDER_EFFECT_CHANGED_RETRY_BLOCKED | The previous operation changed the provider password without a confirmed local commit or rollback. A verify must succeed before the operation is attempted again. |
APPROVAL_EXPIRED | The approval waiting on a human expired. The operation is released rather than resumed: the staged candidate goes back, the record stops blocking, and a fresh submit is the only way forward. |
Identity is read from the item, never from the call#
A sealed directory contract is exactly four keys — provider, tenant, principal object, and account name — written once, plus item-local bookkeeping that never crosses the bridge wire. It lives both in the item's own context and in a separate record that survives an item which does not exist yet, and the two copies must agree.
That separate record declares itself. It carries its own canonical kind rather than the operation record's, so a reader asking for sealed contracts asks for the kind instead of parsing how an id happens to be spelled — and an id is a mutable name, not evidence. Both families still carry the same managed controller, which is what refuses a direct owner write to either. A seal written before that kind existed keeps working unchanged: it still resolves, still lists, and is still protected, and it takes the new kind only when an operator next seals or reseals it.
No lifecycle command accepts that identity as an argument. Skarbiec reads it from the item and puts it on the wire itself, so no caller can rotate one principal's password while naming another. The expectation options can only refuse: they supply nothing, and a value that does not match the sealed contract fails before the bridge is reached. Sealing refuses to overwrite an existing contract; replacing one requires a reseal capability on that exact item, and there is no HTTP route that reseals.
The bridge contract#
The bridge executable must be absolute, owner-controlled, and not a symlink. Skarbiec passes one request object on stdin and accepts only a bounded, sanitized JSON response on stdout, and it speaks exactly one wire version: skarbiec.credential-operation.v3. Earlier wire versions are not accepted anywhere, including inside persisted operation records. The bridge owns the finite mapping from items to provider lifecycle contracts; an unknown item, provider, field, writer consumer, or operation tuple fails closed.
The bridge resolves its Weles admission endpoint through the Stado forward directory on the Weles host loopback, never from an environment URL; an absent or unsafe forward file is an explicit configuration failure rather than an attempt. The Microsoft Entra contract requires a sealed directory contract, always writes password, and accepts only adopt, rotate, verify, and reset. The Microsoft Account contract requires an account address and no sealed contract. Any other combination fails closed instead of guessing.
Beyond status and message, Skarbiec accepts a bounded set of diagnostics and rejects the whole response when any value falls outside it: a machine-readable code of up to 64 uppercase characters, digits, and underscores; a phase drawn from admission, placement, credential read, Entra sign-in, identity verification, password change, fresh login verification, staging, commit, and rollback; whether the request is retryable; the provider effect and rollback status above; the worker host that ran it; and the directory identity acted on, whose mismatch with the sealed contract rejects the response.
The receipt is persisted with the revision it proves#
After a terminal success the receipt is stored inside the item, so status can answer whether exactly this principal was rotated without reading a mailbox or a log. It names the directory identity, the operation, a request id and an evidence digest of 64 hexadecimal characters each, the worker host, and the moments the change and the verification happened. The changed moment may be absent; the verified one may not.
A receipt naming another principal, request, or operation rejects the whole response. For the Microsoft Entra contract, a completed operation without a valid receipt cannot be attributed to the sealed principal, so the item is quarantined rather than committed.
What Weles does not imply#
A stage grant names permission to write one item field. It does not prove that a provider trajectory exists. The finite lifecycle bridge must also register the item, provider, field, writer consumer, and supported operation tuple.
There is currently no documented Figma personal-access-token acquisition contract. A Figma writer grant alone is not evidence that Weles can create that token.