Skip to main content
parmana-connect is a small Node.js service that runs one connector. Parmana decides whether an action may run and signs the decision. It then releases the action to a connector. parmana-connect is a ready-made connector service: it receives the signed release, verifies it, runs the call described by a manifest, and answers. It lives in its own repository, pavancharak/parmana-connect. This page describes it as an operator and a developer use it. For the protocol that connects every external connector to Parmana, read connect any external system. For the reasons behind the design, read why Parmana is built this way.

What it does, in order

  1. Receives a request at POST /execute.
  2. Decides which kind of request it is. A body with release and signature is a signed release from Parmana. Anything else is treated as a signed authorization, and needs the bearer token (see “Two request shapes” below).
  3. Verifies the signed release, with the checks listed in “The checks, in order”.
  4. Runs the connector. The connector makes the upstream HTTP call described in the manifest, using the upstream credential the service reads from its environment.
  5. Answers with the result, in the shape Parmana expects.
The service never decides policy. It checks that Parmana approved the action, and then runs exactly what was approved.

Two request shapes

Both shapes arrive at the same path, POST /execute. The service checks the shape first, then the matching authentication.

The checks, in order

For a signed release, the service runs these checks. It stops at the first failure and returns the failure in errors.
  1. Shape. release.version is 1. businessTransactionId, capability, target, audience and expiresAt are non-empty strings. parameters is an object.
  2. Signature algorithm. signature.algorithm is ed25519.
  3. Key. signature.keyId is default. The public key the service was given is the one checked against.
  4. Signature. The signature is valid over the canonical serialization of release. Any change to the release after signing fails this check.
  5. Audience. release.audience equals PARMANA_RELEASE_AUDIENCE exactly. This stops a release sent to one endpoint from being replayed to another.
  6. Expiry. The release has not expired. The service allows 30 seconds of clock difference.
  7. Replay. The businessTransactionId has not run before. The service records it only after every other check passes, so a release refused for another reason can still be sent again with a fresh signature.
A release that passes every check runs the connector. A release that fails returns 403 with the reasons.

The answer

A successful release returns 200 with:
  • success is true when the connector call succeeded, and false when it failed. Both are 200. Parmana records either.
  • result is an object. If its serialized size is above 16 KB, the service returns success: false with an error saying so, because Parmana refuses results above that size.
  • If the connector throws, the service returns success: false and result.error, with a short message. It never returns the upstream credential.

Settings

Every setting is read from the environment. The service refuses to start if a required one is missing, and the error names the setting, never its value.

The manifest

The manifest says what the connector can do. A minimal one:
  • system is a short name. Every capability id must start with system:.
  • baseUrl must be https, must not contain credentials, and must not contain a query or fragment.
  • credential.type is bearer or header. A header credential also needs headerName.
  • secretRef is an upper-case environment variable name. The service reads the upstream value from it, never from the manifest.
  • Each capability has an id, a method, a path, and an effect of read or write. A GET cannot be a write. A write must declare its inputs. Each {name} in a path must be a declared, required input.

Replay protection

The replay store records every businessTransactionId that has run, until the release expires.
  • postgres (the default) shares the record across every instance of the service. Use it for any deployment that runs more than one instance.
  • memory keeps the record in one process. A restart forgets it, and a second instance does not see it. Use it only for one instance on your own machine or a test.

Deploying

The service needs three things from its host: a way to start it (npm ci && npm run build, then npm run start), the secret files named in its settings, and the environment variables above. On a host that mounts secret files under /etc/secrets/, set PARMANA_PUBLIC_KEY_FILE and PARMANA_CONNECTOR_MANIFEST to those paths. Set PARMANA_SERVICE_HOST=0.0.0.0 and the port the host expects. Set PARMANA_RELEASE_AUDIENCE to the exact public URL of /execute, and register that same URL in Parmana. The service has no health route. Do not set a health check path to /healthz, because the host will mark the service unhealthy. Free hosting plans can sleep when idle. The first request after a sleep took about 50 seconds when we measured it. Parmana’s releases expire 60 seconds after they are issued, so a release sent to a sleeping service can expire before it is answered. For production, use a plan that does not sleep.

Troubleshooting

What the service does not do

  • It does not decide policy, and it does not approve anything. Parmana does both.
  • It does not store the upstream credential anywhere except its environment, and it does not return it.
  • It does not check the business meaning of a request. It checks that the release is authentic, addressed to it, fresh, and new, then runs the manifest’s call.