Skip to content

Reviewer grant trust file

Independent review and authenticated directives are authorised by an lfdg2 grant. The grant is signed with an Ed25519 private key that you hold outside every agent session. Every LumenFlow process verifies it against a public trust file, so no agent process holds a secret that could mint a grant.

1. Generate a keypair outside any agent session

Section titled “1. Generate a keypair outside any agent session”

Generate an Ed25519 keypair in a shell that no agent controls (another OS user or another machine is best). Keep the private key file readable only by you and never place it in a repository, an environment variable, shared memory or a transcript. Derive the raw 32-byte public key as hex and its kid, the lowercase hex SHA-256 of those raw bytes. Record the kid somewhere independent, such as your own notes or a second machine.

2. Install the trust file at the system path

Section titled “2. Install the trust file at the system path”

The file lists public keys only:

{
  "version": 1,
  "keys": [
    {
      "kid": "<sha256 of the raw public key, hex>",
      "public_key_hex": "<64 hex characters>",
      "purposes": ["reviewer", "directive"],
      "not_before": "<ISO timestamp>",
      "not_after": null,
      "revoked_at": null
    }
  ]
}

Install it as root or Administrator, owned by root or Administrators and not writable by group or others:

PlatformSystem path
Linux<system config directory>/grant-trust.json
macOS<system config directory>/grant-trust.json
WindowsC:\ProgramData\LumenFlow\grant-trust.json

Every directory above the file up to the root must also be owned by root or Administrators, not writable by group or others, and not a symlink. No environment variable, workspace config or repository path can supply keys to the enforcing system anchor. A per-user trust file is diagnostic only: it reports trust_level: user-writable and never satisfies a required review. A system file that exists but is unsafe or malformed fails closed and is never replaced by the per-user file.

From your own shell, call issueDirectiveIssuerGrant (the @hellmai/lumenflow-cli/directive-grant-authority export) with privateKeyFile pointing at your private key, plus the reviewer role session, scope key and exactly one WU. The TTL defaults to 15 minutes (1 to 60). The result is an lfdg2.<payload>.<signature> token, single use.

The reviewer records the verdict with the grant:

pnpm wu:review --id WU-XXXX --record --verdict approved \
  --reviewer-session <reviewer-session-id> --scope-key <scope-key> --grant <lfdg2-token>

No signing key is needed in this shell: verification reads only the public trust file.

A grant is signed for exactly one purpose (reviewer or directive) and each boundary refuses the other. A trust file that repeats a kid, or lists no keys, is refused as malformed, so to revoke a key edit its existing entry (set revoked_at) instead of appending a second one. A grant may span at most 60 minutes between issued_at and expires_at. Because retiring a key with not_after does not stop a leaked key from signing new short grants, revoke any key that may have leaked.

  • Rotate: add the new key to the trust file and set not_after on the old key. A grant is judged by its own issued_at, so reviews recorded before the rotation keep verifying.
  • Revoke: set revoked_at on a compromised key. Every grant of that key is then refused, even recorded evidence, and affected work units need a fresh review under a new key.
  • The file is re-read on every verification, so both take effect at once.
CodeMeaning
grant_legacy_hmac_refusedAn lfdg1 HMAC grant. Issue an lfdg2 grant instead.
grant_tamperedThe signature does not verify.
grant_expiredThe grant’s own expiry has passed.
grant_missing_trust_anchorNo safe system trust file (absent, unsafe, or only a user file).
grant_untrusted_issuerThe key is unknown, outside its validity window, or revoked.
grant_wrong_purposeThe grant was signed for the other purpose (reviewer versus directive).
grant_malformedThe token is not the one canonical spelling of a valid grant.
grant_replayedThe grant was already consumed.