Skip to content
Registry StackDocsLatest

Configure a script source adapter

View as Markdown

Use the script capability when one fixed HTTP request is not enough. Rhai is the v1 script runtime, but the capability is product-neutral. A DHIS2 label or version does not enable it, and an unknown source product can use exactly the same host API.

The worker has no network, filesystem, environment, subprocess, clock, random, credential, or production logging access. It asks the Relay parent to make each bounded source call. Relay validates the complete request against the authored same-origin authority, commits the request before dispatch, consumes the next durable call permit, and returns only bounded JSON or text.

The DHIS2 Tracker starter is one concrete script example. Its product name and version are review evidence only; any reviewed source system can use the same script capability and host API.

Run these commands in order. The focused test prints a safe synthetic trace. The next command starts the same fixture as a watch smoke; press Ctrl+C after its initial passing report. The final three commands test every fixture, explain the redacted plan, and build the unsigned product inputs.

dhis2-tracker: DHIS2 Tracker

A product-neutral script adapter applied to a bounded DHIS2 Tracker read journey.

registryctl init --from dhis2-tracker --project-dir dhis2-project
registryctl test --project-dir dhis2-project --integration health-record --fixture complete-health-match --trace
registryctl test --project-dir dhis2-project --integration health-record --fixture complete-health-match --watch
registryctl test --project-dir dhis2-project
registryctl check --project-dir dhis2-project --environment local --explain
registryctl build --project-dir dhis2-project --environment local

Generated from the five starter workspaces compiled into registryctl.

The public integration declares input schemas, authentication interface, allowed source paths, the script file, outputs, and only the limits that differ from the defaults:

version: 1
id: person-status
revision: 1
input:
person_id:
role: selector
type: string
maxLength: 64
source:
product: fictional-registry
versions: { unverified: [project-api-v1] }
auth: { type: static_bearer }
allow:
- method: GET
path: /v1/people/**
response:
format: json
response_headers: [Link]
capability:
script:
file: adapter.rhai
outputs:
active: { type: boolean }
programme_code: { type: [string, "null"], maxLength: 32 }

Source product and version are interoperability evidence only. Runtime dispatch uses the generic source, capability, and environment contracts.

Every input lives in one typed map and declares role: selector or role: parameter. An integration has one to eight non-null selectors and at most sixteen total inputs. The canonical selector values share a fixed 4096-byte ceiling. A nullable parameter is still present in the consultation request as explicit JSON null; the adapter receives the validated value beneath ctx.input.

An asterisk admits one non-empty path segment. A terminal double asterisk admits zero or more complete non-empty segments. Matching is case-sensitive. Dot segments, encoded separators, fragments, user information, and another origin fail before credentials are resolved. A source POST also requires semantics: read_only and evidence that the admitted operation is non-mutating.

The defaults are five calls, 64 KiB of aggregate author-controlled request bytes, a 512 KiB per-response body, 2 MiB of aggregate source-response bytes, and a 15 second deadline. The hard ceilings are 16 calls, 1 MiB of request bytes, 8 MiB per response, 16 MiB of source responses, and 60 seconds. Deployment settings may lower these limits but cannot widen the reviewed integration.

Create adapter.rhai with one consult(ctx) function:

fn consult(ctx) {
let target = source.path(
"/v1/people/{person_id}",
#{ person_id: ctx.input.person_id }
);
let response = source.get(target, #{
query: #{ fields: "id,active,programmeCode" }
});
if response.status == 404 {
return result.no_match();
}
if response.status != 200 {
return result.fail(failure.source_rejected);
}
if response.body.id != ctx.input.person_id {
return result.fail(failure.subject_mismatch);
}
result.match(#{
active: response.body.active,
programme_code: response.body.programmeCode
})
}

The host API provides source.path, source.get, source.post_json, source.post_form, the three successful result constructors, and fixed source failure constants. Every response has status, body, and the safe headers named by source.response_headers. The body is bounded decoded JSON by default. Set source.response.format: text when the reviewed endpoint returns text. Non-2xx statuses return normally except 401, 403, and 429, which remain host-owned failures. JSON null is Rhai unit. A match returns every declared output with its exact type and nullability.

An adapter may branch, transform collections, repeat a request, and follow a bounded same-origin pagination link. Relay canonicalizes an absolute returned link back to a relative source reference, rejects another origin, and reapplies the path, method, header, request, response, call, and deadline checks before dispatch. Each call is independently authorized and counts against the same cumulative budgets.

For a larger adapter, list up to 32 project-local Rhai files in their deterministic evaluation order:

capability:
script:
file: adapter.rhai
modules:
- lib/normalization.rhai
- lib/cardinality.rhai

Local modules contain ordinary top-level Rhai functions. They cannot add source authority or load another file at runtime. registryctl resolves each normalized .rhai path beneath the integration directory without following symlinks, combines the declared modules with the entrypoint, and enforces a 64 KiB compiled-script ceiling. Module paths, order, and contents are hash-covered by the authored and generated closures, so a module edit changes the review and deployment inputs. There is no ambient module search path, remote package resolution, or runtime import fallback.

Committed fixtures must be wholly synthetic and prove the rendered request as well as response:

name: match
classification: synthetic
input:
person_id: SYNTHETIC_PERSON_001
interactions:
- expect:
method: GET
path: /v1/people/SYNTHETIC_PERSON_001
query:
fields: id,active,programmeCode
respond:
status: 200
body:
id: SYNTHETIC_PERSON_001
active: true
programmeCode: DEMO
expect:
outcome: match
outputs:
active: true
programme_code: DEMO

Keep match, no-match, ambiguity, identifier-mismatch, and meaningful project variations. The harness derives malformed decoding, limits, timeout, authorization-before-source, output minimization, and protocol-verification negatives. Large synthetic bodies belong in fixtures/bodies/.

Terminal window
registryctl test --project-dir <project> --integration person-status --fixture match --trace
registryctl test --project-dir <project> --integration person-status --watch
registryctl check --project-dir <project> --environment <environment> --explain

Offline tests are portable and use no real credentials or network. Production activation remains Linux-only while the process controls are code-owned. Fixture success proves the adapter against synthetic observations, not production source interoperability or deployment approval.

SymptomCauseFix
A source call fails before dispatchThe method, path, target, header, query, body, or remaining budget is outside authored authority.Narrowly admit the real read operation or correct the adapter.
A terminal result is rejectedThe adapter omitted an output or returned a wrong name, type, nullability, or size.Return the complete declared output map.
A fixture response matches but its request proof does notIts expected interaction differs from the production canonical request.Update the synthetic request expectation or fix the adapter.
Production reports the worker unavailableThe adjacent code-owned worker or required Linux isolation is absent.Correct the deployment. Do not configure an in-process fallback.

Return to Author an HTTP Registry Stack project to review and build the project.