Skip to main content
An agent proposes an action and the facts it rests on: “release the goods, the invoice is paid”. That the invoice is paid is the agent’s claim. This page connects the system that owns the fact, so Parmana asks it and decides on its answer (Business validation, RFC-0023). You run a small HTTPS endpoint in front of the system. For each fact a policy declares from your source, Parmana sends it a signed query: what to establish (claim) about which business object (subject). The endpoint checks the query came from Parmana, reads the fact from its own records, and returns a signed answer. Parmana checks the answer against the key you registered and records it as a Trusted Signal. Any source works: it only has to answer this protocol. Parmana holds none of the system’s credentials.
Status, 2026-10-11. Registering a source through maker checker, the signed protocol and deciding on a verified answer are implemented and tested in the repository (docs/CLAIMS.md 2.54). They are not deployed until this change is merged and deployed, and no real business system has been connected yet (docs/VERIFICATION-GAPS.md G-92). The SDK methods for the routes below ship in the next SDK release; until then, call the routes directly.
Follow it literally. If your situation is not covered, stop and ask the operator.

Rules that apply to every step

  1. Do the steps in order. Compare each response with Expect; if it differs, follow If not and stop.
  2. The endpoint answers only from its own records. It never answers from anything in the query except claim and subject.
  3. The endpoint answers only queries it verified. Anything else gets 401 and no fact.
  4. Two different people propose and approve every change.

Inputs you need before you start

Step 1: make the source’s signing key

Do: on the machine that will run the endpoint, make an Ed25519 key pair. The private key never leaves it.
You can register a source without a key. Then Parmana trusts its answers on the strength of the pinned HTTPS connection alone, and the Trusted Signal does not prove the source said it. Register the key.

Step 2: run the endpoint

Do: run typescript/examples/08-business-signal-source.ts in front of your system, replacing lookupInvoice with a read from your records. Parmana’s public key is the pem field of GET {PARMANA_URL}/keys/default.
Serve it over HTTPS behind your TLS terminating proxy. Parmana asks only https endpoints. Expect: the endpoint answers a GET with 405.

Step 3: propose the registration

Do: as the maker, POST {PARMANA_URL}/business-signal-sources/changes:
Expect: 201 with "status": "PENDING_APPROVAL", the endpoint as Parmana stored it, and the key in canonical SPKI PEM. Keep changeId. If not: INVALID_BUSINESS_SIGNAL_SOURCE names the field; EXTERNAL_ENDPOINT_ADDRESS_REFUSED means the endpoint is not https, names an address or localhost, or resolves to an address that is not public; 409 means the name is already registered or has a change pending. See Propose a business signal source change.

Step 4: approve it

Do: as the second person, sign a step up authorization for changeId and action approve, then POST {PARMANA_URL}/business-signal-sources/changes/{changeId}/approve with { "stepUpAuthorization": … }. Expect: 200 with "status": "APPROVED". GET /business-signal-sources lists the source as active. If not: SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE means the maker tried to approve; EXTERNAL_ENDPOINT_ADDRESS_REFUSED means the host’s address changed since the proposal. The change stays pending.

Step 5: declare the fact in a policy

Do: in the policy that governs the action, name the source for the fact, and take it out of anything the agent declares. Approve the policy change through policy governance.
Expect: GET /policies/in-effect lists invoicePaid under signals.sourced.

Step 6: send a request

Do: have the agent send the request as usual. It need not send invoicePaid; if it does, the ERP’s answer must equal it. Expect: the decision’s assessment.businessValidation.status is VALID, and its signals hold a Trusted Signal with source: "erp", the invoice as subject, the ERP’s value as observedValue, and integrityProof.sourceProofVerified: true. If not: the request is refused, with the reason in assessment.businessValidation. See the table below.

The protocol

Parmana POSTs to the endpoint:
signature.value is Parmana’s base64 signature over the canonical JSON of query (the SDKs’ canonicalSerialize), with the key at GET /keys/default. Check it, check audience is your URL as registered, check expiresAt has not passed, and answer each nonce once. signature.algorithm is the algorithm of that key: ed25519 unless the deployment chose another (Choose a signature provider). The example accepts only ed25519. The endpoint answers 200:
answer echoes source, nonce, signalKey, claim and subject from the query, so an answer cannot be replayed to another question. status is observed with value and observedAt (and optionally validUntil), or not_found or conflicting with a reason. signature.value is the base64 Ed25519 signature over the canonical JSON of answer.

What each answer does

Every status but VALID refuses the request; nothing is executed, and the Refusal Record carries the reason.

Revoke or move a source

Propose { "action": "revoke", "name": "erp", "reason": "…" } and have a second person approve it. From then on, policies that name erp are refused as SOURCE_UNAVAILABLE. To move the endpoint or rotate the key, revoke, then register again. The revoked registration stays listed.

Limits

  • An answer is checked when the request is decided, and its validUntil again at release. An external connector also receives it as a signed condition, so its endpoint can act only if it still holds (Connect any external system).
  • Parmana checks the source signed the answer. It cannot check the source’s records are right: a source answers for its own data.
  • See Limitations, G-92.