Skip to main content
A running Parmana deployment needs four kinds of credential. This page gives every command, in Windows PowerShell, from creating the keys to approving a policy and signing a refund approval. Every command here was checked against the scripts and routes it names.

The four credentials

What the server never holds: a raw API key, a step up private key, or an approver private key. A key shown once by a script cannot be recovered; if it is lost, you make a new one.
Each person creates their own credential on their own machine. If one person creates the checker or approver key for someone else, that person holds both halves of maker and checker. The server checks that the approving caller differs from the proposing caller (SameActorCannotApproveOwnChangeError). It cannot tell whether two callers are two people. If one person holds both, say so: it is one person with two credentials, not two person control.

Before you start (every machine, once)

You need Node.js 24 or later and Git.
npx tsc -b builds the packages the scripts import. Without it every script fails with ERR_MODULE_NOT_FOUND ... @parmana\shared\dist\index.js. Each key script prints its secret once. The blocks below save everything to files automatically, so only the printed Key : value needs to go into a password manager. Keep the files outside the repository folder: the repository is public.
parmana-entries holds one JSON line per caller: the caller id, the key hash and, for a checker, the step up public key. None of it is secret. It is what you deploy.

1. Maker key (the maker runs this)

Replace maker-NAME with the maker’s caller id.
Save the printed Key : value in a password manager now. --credential-holder-type USER is required: policy governance endpoints accept only callers provisioned as human (packages/api/src/auth/isHumanCaller.ts), and a key without it is refused with NON_HUMAN_CALLER_DENIED.

2. Checker key (the checker runs this, on their own machine)

3. Approver key pair (the approver runs this, on their own machine)

It writes two files, and refuses to overwrite existing ones:

4. Agent key (the operator runs this)

Grant only the capabilities the agent needs. For a refund agent:
The key goes into the agent’s own configuration. With no --allowed-principal-ids, the agent may only act as itself: the principalId in its requests must equal its caller id.

5. Deploy the API keys (the operator runs this)

PARMANA_API_KEYS is a JSON array of the entries. This block builds it from every file in parmana-entries, replaces the variable on Vercel and redeploys, because an environment change only takes effect in a new deployment. The first time, it links a folder to the Vercel project (run vercel login before).
Every caller not in the folder loses access at that redeploy. That is how a key is revoked. Keep the parmana-entries folder: a sensitive Vercel variable cannot be read back, so this folder is the only copy of the list. Do not use scripts/rotate-caller-key.mjs for this. It rewrites an entry as only a caller id and a hash, dropping credentialHolderType and stepUpPublicKey, so a human caller would lose access to policy governance.

6. Test a key

GET /callers/me returns the caller id of a key, and never the key itself.

7. Maker and checker: change a policy

Propose (maker)

The body needs reason and proposedContent, the whole policy.json, whose policyId must match the name in the path. Propose a new version folder (an approved version is never edited); customer-refund 1.2.0 is in effect, so the next would be, for example, 1.3.0.
The response holds pendingPolicyChangeId. Send it to the checker.

Review (checker)

Read every rule, and every warning and conflict. Rules are evaluated first match wins, so an overlap between two rules with the same outcome is harmless, while an overlap between an approve rule and a reject rule decides behavior by their order. If anything is wrong, do not approve.

Approve (checker)

The step up signature is valid for 120 seconds: run the whole block at once.
The response shows status : APPROVED. The server verifies the envelope against the step up public key in the approving caller’s own entry, not against --key-id. From then on, the approved version is the one in effect for its action, with no deploy (docs/CLAIMS.md 2.43).

8. Register an approver

There are two ways. Use the first; it needs no code change and no deploy. Through maker checker (recommended). The maker proposes the public key, the checker approves it with a step up signature, and it applies to the next request. Every command, in curl, TypeScript and Python, is in Manage approvers:
The checker then approves changeId exactly as in step 7’s approve block, with the URL $url/approval-issuers/changes/$changeId/approve (the step up signature is made the same way, with --pending-policy-change-id $changeId). GET /approval-issuers then lists the key with source: governed and revoked: false. In code, with a pull request. Add the entry with the public key file’s contents to TRUSTED_APPROVAL_ISSUERS in packages/api/src/bootstrap/codeApprovalIssuers.ts:
Open a pull request; merging deploys it. Until an approver is listed, every signed approval is refused. To revoke an approver, set revoked: true and deploy.

9. Approver: sign one approval

For one action on one resource, up to an amount, valid 15 minutes by default, usable once:
The agent sends a new request with managerApproved: true and the contents of approval.json in signals.approvalArtifact. See Human approval.

10. Rotate or revoke a key

Rotate at once if a key was pasted into a chat, a ticket or a log.

Rotating the refund agent service

The refund agent service (parmana-paytm-agent) holds three values tied to Parmana. Change them together, or refunds fail in between: Order: set all of them on both Vercel projects, then redeploy Parmana and the agent one right after the other. A new value takes effect only in a new deployment. Afterwards: GET /callers/me with the new agent key returns its caller id, and a request to /agent/refunds without a key returns 401. Only a real refund proves the shared secret end to end.

What was done on 2026-09-27

The production deployment was rebuilt with these steps, in this order:
  1. The operator had no working human key, and two keys had been shown in a chat transcript. Every production API key was replaced from scratch with step 5; the old policy-maker, policy-reviewer-1 and paytm-refund-agent keys stopped working at the redeploy.
  2. The operator decided to hold the maker, checker and approver roles for now. New callers: charak1987 (maker), reviewer-charak1987 (checker, with a step up key) and paytm-refund-agent (paytm:refund only). Governance in production is therefore one person with two credentials until a second person replaces the checker.
  3. reviewer-charak1987 reviewed and approved customer-refund 1.1.0 (change 008f504d-0efd-4bec-b33a-2991bb84099f, proposed by policy-maker) at 18:53:40 UTC. Refunds then ran under 1.1.0: automatic up to 10000, a verified manager approval above 10000 and up to 100000, refused above 100000. Since 2026-09-28 customer-refund 1.2.0 is in effect: no refund runs without a signed manager approval.
  4. The refund agent key was deployed after the approval, so no refund ran under 1.0.0 with the new key, and the refund agent’s Vercel project was given it.
  5. A refund manager key pair, manager-charak1987, was created and, on 2026-09-28, added to TRUSTED_APPROVAL_ISSUERS (step 8). Before that, every production refund above 10000 was refused.
The record is docs/REMAINING-WORK.md section G.

What was done on 2026-09-30

Every credential was made again from scratch, in this order:
  1. New keys for charak1987 (maker), reviewer-charak1987 (checker, with a new step up key pair) and paytm-refund-agent, a new AGENT_API_KEY and a new PAYTM_CONNECTOR_SHARED_SECRET. Both Vercel projects were changed and redeployed together. Each new key returned its own caller id from GET /callers/me; every old key returned 401.
  2. A new refund manager key, manager-charak1987-key-2, was added through maker checker (step 8), proposed by charak1987 and approved by reviewer-charak1987. manager-charak1987-key-1, listed in code, was revoked by PR #88 (revoked: true), merged and deployed the same day; its private key was deleted.
  3. The old key files were deleted.
  4. The 13 policy versions of the G-80 change (every action needs a signed approval, reads included) were proposed by charak1987 and approved by reviewer-charak1987, then merged (PR #87) and deployed. connector-capability 1.1.0 was approved a second time: the first proposal was read with Windows PowerShell 5.1 Get-Content -Raw, which changed an em dash in its description, so its hash did not match the file and CI refused the merge. The commands on this page read files as UTF-8 for that reason.
The same person still holds the maker, checker and approver credentials.

Troubleshooting