stable

sealing provider id the pid string, semver-frozen once a provider ships v1.x

Reference rule: a sealing provider's pid string is semver-frozen from v1.x, and how hub detects a mismatch when a vault reopens under a different provider.
Subpath@noy-db/at-* providers (the pid string)
Factory— reference rule; no API of its own
Layerwith-party
Kindreference
ContractNoydbSealer — see the @noy-db/at-* family overview

This page assumes you're already using managed-secret mode — the @noy-db/at-* family (env var, OS keychain, cloud KMS) that lets a server open a vault without a human typing a secret. In that mode, @noy-db/hub (the always-on core library every noy-db app imports) generates a random secret once, hands it to your chosen sealing key provider to encrypt ("seal"), and persists the sealed result inside the vault at _meta/sealed-secret — a small JSON record called the sealed envelope. Every provider publishes a pid (provider id): a short string, unique to that provider instance, stamped into the envelope alongside the sealed bytes.

This page captures the one rule that governs that pid string across the at-* family, and why it's frozen once a provider ships. It has no opt-in of its own — it governs providers you've already wired in via sealingKey in createNoydb (see the family overview linked above for that snippet).

The rule

Each at-* provider owns its pid format. Once a provider ships v1.x, the pid format is semver-frozen — changing it is a major-version break of that provider package, treated with the same discipline as a public API break.

Concretely, every at-* package:

  1. Fixes its pid format as a readonly id: string on the object the provider factory returns — e.g. @noy-db/at-env's atEnv() returns id: \env:${envVar}`, @noy-db/at-aws-kms's atAwsKms()returnsid: `aws-kms:${keyId}`. Today only @noy-db/at-macos-keychain` additionally documents its format in the package README, under a "Provider id format" heading; for the other four providers the format lives in source and in the table below.
  2. Locks the format with a golden-string test (e.g. at-env's pid-stability.test.ts, at-macos-keychain's expect(p.id).toBe('macos-keychain:com.acme.app/alice@acme.example')), so an accidental rename fails CI instead of shipping silently.
  3. Treats any future format change as a major-version bump with a migration story, not a patch.

Current formats

Packagepid formatExampleTest location
@noy-db/at-envenv:{envVar}env:NOYDB_SEALING_KEYpackages/at-env/__tests__/pid-stability.test.ts
@noy-db/at-macos-keychainmacos-keychain:{service}/{account}macos-keychain:com.acme.app/alice@acme.examplepackages/at-macos-keychain/__tests__/at-macos-keychain.test.ts
@noy-db/at-aws-kmsaws-kms:{keyId}aws-kms:arn:aws:kms:us-east-1:1:key/abcpackages/at-aws-kms/__tests__/at-aws-kms.test.ts
@noy-db/at-gcp-kmsgcp-kms:{keyName}gcp-kms:projects/my-project/locations/global/keyRings/ring/cryptoKeys/keypackages/at-gcp-kms/__tests__/at-gcp-kms.test.ts
@noy-db/at-azure-keyvaultazure-kv:{keyId}azure-kv:https://my-vault.vault.azure.net/keys/noydb-sealing/abc123packages/at-azure-keyvault/__tests__/at-azure-keyvault.test.ts

If you build a custom provider, follow the same <family>:<scope> shape and add a row here once it ships.

Why this matters

A sealed envelope persisted at _meta/sealed-secret looks like this:

{
  "v": 1,
  "_noydb_sealed": 1,
  "pid": "env:NOYDB_SEALING_KEY",
  "payload": "<base64 sealed bytes>"
}

The pid field is the only thing your app has to check that it's opening a vault with the provider that originally sealed it. If @noy-db/at-env v1 emits env:NOYDB_SEALING_KEY and a hypothetical v2 started emitting at-env:NOYDB_SEALING_KEY, every existing vault sealed under v1 would look like it was sealed by a different, unrecognized provider.

Concretely: when you reopen a managed-secret vault, hub reads the envelope and compares its pid to the .id of the NoydbSealer you passed to createNoydb. On a match, it calls that provider's unseal(). On a mismatch, it throws before touching any key material, naming both the envelope's pid and the current provider's id in the error — there's no silent fallback to a "closest matching" provider. This is a single point-in-time comparison, not a registry that dispatches across many installed providers, so a pid rename is indistinguishable, from hub's point of view, from swapping in an entirely different provider.

This is a stronger discipline than typical npm packages because the artifact (the sealed envelope) lives on disk indefinitely — often longer than the package's release cadence. A pid change is more like a database schema migration than a code-level API tweak.

Analogue: to-* adapter resource triples

The to-* storage adapter family follows the same discipline for the (vault, collection, id) triple every store's get/put/delete takes: once shipped, the format is frozen. Changing it would orphan every record on disk written under the old shape. The reasoning is identical for pid.

If you ever genuinely need to change pid format

Because hub only ever checks one configured provider's id against the envelope's pid, a rename isn't something hub does for you — it's a migration you'd build into your own app and your provider's next major version:

  1. Ship a new provider version and, in your app, try unsealing under the new pid first, falling back to the old provider/pid for vaults not yet migrated.
  2. Deprecate the old pid over a release window, documented in the provider's CHANGELOG.
  3. Migrate lazily: the first time a vault opens successfully under the new provider, reseal the secret and overwrite _meta/sealed-secret with a new-pid envelope, dropping the old one.
  4. After the deprecation window, drop the old-provider fallback from your app entirely.

This is genuinely expensive — it means running two providers side by side in your app during the transition — and should be reserved for actual format problems, not cosmetic preference. Most "I want to change the pid" requests are better resolved by NOT changing it: the format is opaque dispatch metadata, not user-facing copy.