secrets
The minimal keystore: the one place a credential's value is reached. Programs, feature documents, deployment documents and audit records carry a HANDLE, handle:<provider>/<path>; the value exists only inside this module's resolve seam and the one operation a consuming module hands it to. Providers are deployment rows, [secrets [provider name= kind= …]] in the deployment's [runtime]: sealed (values sealed one by one under AES-256-GCM with the handle and the slot as associated data, in one [sealed-store v=1 alg=aes-256-gcm] document, under a 32-byte master key only the client holds — key-from=env:<VAR> or a mode-0600 key-from=file:<path>) and env (the process environment, the development profile only). The seam is internal and every resolution passes authz's one decision function — [authz-request [actor [module <consumer>]] [capability secrets:resolve] [slice <handle>]] against the deployment's [authz] grants — and writes one [audit module=secrets action=:resolve] record whose [secret-resolved handle= consumer= at=] detail never carries the value, on success and on every refusal. Rotation lives in the provider (sso.md §3.4's window): a handle resolves to its active value. No public verb answers a value; a later custody rung (keychain, KMS, vault) is a provider row, and handles, consumers, the PEP and the record never move.
secrets:open
[$secrets:open] ($deployment::element $opts::map {}) -> element — Boot the keystore for a deployment: validate every [secrets [provider]] row (a kind that could export a value is CXER7004, an env row under served: true is CXER7005, a row that cannot serve is CXER7006), require the audit sink, and seed the PEP's basis from the deployment's [authz] grants. The answer is a [keystore] that names providers and never holds a value.
[?lib 'cx-platform/secrets']
[$secrets:open [deployment name=dev [runtime [secrets [provider name=crm kind=plaintext]]]]]
cx-err:CXER7004
secrets:with-resolved
[$secrets:with-resolved] ($keystore::element $handle::string $consumer::string $op $opts::map {}) -> any — Resolve a handle through the internal seam for a consumer module — the grammar (CXER7000), the provider (CXER7002), the PEP (CXER7001), the value (CXER7003), in that order, with one audit record either way — and hand the secret-typed value to the ONE operation $op; answer the operation's answer, refusing CXER7007 when it would carry the secret itself. opts.now is the instant the record and a rotation window read.
[?lib 'cx-platform/secrets']
[$secrets:with-resolved [keystore profile=development] 'secret:crm/sandbox' 'cx-platform/connector' [?fn ($s) true] {now: '2026-09-26T09:00:00Z'}]
cx-err:CXER7000
secrets:put
[$secrets:put] ($where::element $handle::string $value::string $opts::map {}) -> element — Store a value under a handle in its sealed provider: seal it AES-256-GCM as the handle's active slot and write the store. Acts on a sealed provider only (an env provider is CXER7002). The answer is a [put handle= provider= slot=] receipt — never the value.
[?lib 'cx-platform/secrets']
[$secrets:put [keystore [provider name=os kind=env]] 'handle:os/home' 'value']
cx-err:CXER7002
secrets:rotate
[$secrets:rotate] ($where::element $handle::string $value::string $opts::map {}) -> element — Set a handle's next value and open its rotation window inside the sealed provider: [rotation handle= overlap= not-after=], the deadline opts.now plus opts.overlap. The seam answers the active value until the deadline and the successor from it; the answer is the window, never a value.
[?lib 'cx-platform/secrets']
[$secrets:rotate [keystore [provider name=os kind=env]] 'handle:os/home' 'value' {overlap: '24h'}]
cx-err:CXER7002
secrets:list
[$secrets:list] ($where::element $provider::string) -> [sequence string] — List the handles a sealed provider holds — handles only, each once, in order.
[?lib 'cx-platform/secrets']
[$secrets:list [keystore] 'crm']
cx-err:CXER7002