Where packages come from
jadpo package add <name> looks for a package in a fixed order and records which step it used, so an agent never improvises where to look:
On this page
jadpo package add <name> looks for a package in a fixed order and
records which step it used, so an agent never improvises where to look:
- The registry, the official package first.
- The provider's published specification (OpenAPI, Smithy, GraphQL), given a URL or found in its known places, imported once.
- An importer from the official SDK's type definitions, a Postman collection or a HAR capture.
- Written from the provider's documentation by an agent.
It also takes a git repository (jadpo package add github.com/acme/acme-pricing@v2.1.0, below) and another Jadpo app's
OpenAPI document (jadpo package add ../orders, see
Packages from other Jadpo apps). Each
source is read once, when the package is added or updated. Then it is
pinned in jadpo.lock with its source and digest.
The registry
The registry is Jadpo's own, and holds Jadpo packages of both kinds.
- Jadpo source only. A package carries no native code and no install script, and nothing in it runs when it is installed. Its code runs only when started (What package code may do). A malicious package could still name the wrong host or mark an unsafe call idempotent, so hosts, services and every package change appear in the approval report, the summary a person signs off before a release.
- Checked on upload. The registry repeats every install check. It adds discovery, curated packages marked official, publisher verification and signing. It renders each package's documentation website from its surface, so every package is documented the same way.
- A few curated packages, Stripe, Twilio and GitHub, imported from their published OpenAPI, live in the Jadpo repository.
An importer
jadpo package import <format> <file> turns a published description into
the app's own integration once, offline, in
shared/providers/<name>/<version>/:
| Format | Typical source | What it gives |
|---|---|---|
openapi |
Most REST APIs (Stripe, Twilio, GitHub), and every Jadpo API | Operations, types, error responses; from a Jadpo app, exact (its x-jadpo fields) |
smithy |
AWS, one model per service | The same, plus AWS's protocols and signing |
postman |
A provider's published Postman collection | Operations and example bodies; types inferred from examples |
har |
A browser or proxy capture of real traffic | Operations and real responses; values stripped, shapes kept |
dts |
The provider's official TypeScript SDK (.d.ts files) |
Exact types and SDK method names; no HTTP method or path, so the agent adds those from the docs |
openapi reads a local file: jadpo package import openapi ../orders/build/openapi/main.json, with --as Orders.v1 to name the
package. "How the OpenAPI importer reads a document", further down this
page, says how it reads one.
An agent then fills in what the source leaves out: which calls change
something, idempotency, error names, and decoding (OpenAPI often says only
integer for Unix seconds). A person reviews that diff. Corrections to a
wrong specification live in the package and survive the next import,
because the importer merges rather than overwrites, as an
OpenAPI Overlay does.
Written by hand, when there is no specification
Many APIs publish only HTML docs. A package is Jadpo source, so for these an agent writes the package files directly from the docs, in exactly the format an importer produces. Nothing else changes: the same compiler checks, the same fakes, the same review. A package needs only the operations the app uses, so it can start with one operation and grow.
Mapping imported types to standard types
An imported description says little about what a field is: OpenAPI's
integer may be pence, Unix seconds or a count. A package's public types
use the standard types, such as Money and Instant, where one fits
(type.standard_exists, Standard library). So
an import maps fields to them in six layers, and nothing is guessed
silently:
- Exact mappings. A description's own format hints map without
judgement: OpenAPI's
date-timetoInstant,datetoCalendarDate,emailtoEmail,uritoUrl,uuidtoUuid,ipv4andipv6toIpAddress, ISO 4217 and ISO 3166 enumerations toCurrencyandCountry. - Shape and name detection, with the same matcher as
type.standard_exists:amountwith a siblingcurrencytoMoney,lat/lngtoPoint, an integer named like a time (created,*_at) as a candidateInstantfrom seconds. The provider's own shape becomes a named decoding at the edge (Stripe'samount: 1050, currency: "gbp"decodes toMoney, with a field'swire_parts:under "Decoding"), so the app only ever sees the standard type. - Findings on what was flagged. Every candidate the importer is not
certain of is a declinable error on that field,
package.standard_candidate, whosenext:says what to check, marked(agent): confirm against the docs and swap, or decline with a reason thatjadpo approvalshows. - One scoped review of the whole package. The import ends with one
broad instruction to the agent: go through every imported type the app
reaches against the provider's documentation, use the standard type
wherever one fits, add a decoding wherever a format is undocumented
(seconds, cents, comma-separated lists), and mark which operations change
something and which are idempotent. The review covers the operations the
app calls and every type they reach, never the whole description
(Stripe's has 1,180 types), and grows when the app calls something new.
Each imported type starts unreviewed; the agent marks it reviewed when it
has gone through it;
jadpo checklists reviewed and outstanding types for the operations the app calls, andjadpo approvalshows a person any type shipped unreviewed. - Confirmation by real responses. An operation the app calls stays
unconfirmed until
jadpo package recordhas checked a sandbox response against it ("Provenance and confirmation", below). - The safety net. The package is Jadpo source, so
type.standard_existsruns on it as on any code: a field that looks like a standard type and was not mapped blockscheckuntil it is swapped or declined.
The import's own output lists what it mapped, the findings, the scoped
review instruction and the operations still unconfirmed, with --json for
agents. Corrections made in review survive the next import, because the
importer merges rather than overwrites.
The agent marks a type reviewed by listing it in
provider.jadpo'simported:,reviewed: [Charge, Shop], each one of the package's own types, once (package.provider_invalid). The review's scope is worked out from the calls on every check, so it grows when the app calls something new: the package's types each operation the app calls takes and gives reach, through fields, list items, payload variants and refinements.jadpo check, once it passes, lists the types still to review and the calls no recording confirms, counts both in its footer, and gives every call and type in--jsonasproviders; neither stops it. With nothing left a passing check is one line; with something left it reads:$ jadpo checkTo review Go through each type against the provider's documentation, then add it to `reviewed:` in `imported:` in its package's `provider.jadpo` (agent) Tidepay.v2024_06_20 Charge, ChargesGetChargesInput, Shop; 0 of 3 reviewedNot yet confirmed Record each with `jadpo package record`, which checks a sandbox answer against its types (agent) payments.charges.get_charges✓ checked 3 to review · 1 unconfirmed · 9 filesIt writes the same to
build/report/providers.mdand.json, forjadpo approvalto show a person what ships unreviewed or unconfirmed. The import ends with the scoped instruction: the types the app's calls reach still to review, or, while it calls none of the package's operations, when the review starts; and the calls not yet confirmed. Importing again keepsreviewed:, less any type the description added to, which the summary lists under "To review again". The choices are in "The scoped review and unconfirmed operations" below.
Provenance and confirmation
Every operation says where it came from, so a reviewer knows how far to trust it:
source: |
Meaning | Also records |
|---|---|---|
PackageSource.imported |
From a published description (OpenAPI, Smithy, Postman, .d.ts, GraphQL introspection later) |
provider.jadpo's imported: names the file and its digest |
PackageSource.written |
Written from the provider's docs | docs: with the page's URL |
An operation is confirmed when a real response has been checked against
it. jadpo package record <connection> <operation> calls the provider's
sandbox once, then checks the operation's output type against the response,
or writes the type if there is none yet. It saves the response, with values
replaced by placeholders, as recordings/<resource>.<operation>.json in the
package; every decoder must decode every recorded response, on every build
of the app's own integration and at publish. An operation imported from a
HAR capture is confirmed from the start. record refuses a connection whose
environment is live.
Written operations that are not confirmed are allowed in every build. The approval report flags them, and CI requires a passing contract run against the sandbox for each one (see "Tests and fakes").
jadpo package record <connection> <resource>.<operation>calls the sandbox once through the connection, with its credential from.env.localand the operation's input, written as a route's body is, from--input. It checks the answer against the operation's output type and keeps it as the recording: alongoutput_at:, only the members the type declares, each plain text or number replaced by a placeholder naming its built-in type ("{Text}"), and what the package's own encodings and decoders read kept as sent. The surface then marks the operationconfirmed, andjadpo testdecodes the recording ("Recording errors"):$ jadpo package record couriers bookings.create --input '{"postcode": "N1 9GU"}'✓ Recorded `couriers.bookings.create` from the sandbox sent POST https://sandbox.acme-couriers.example/v1/bookings answer decodes as Booking values 2 placeholders, 2 kept as sent file shared/providers/acme_couriers/v1/recordings/bookings.create.json✓ operation recorded AcmeCouriers.v1 bookings.create · confirmed→ jadpo test decodes the recording with every decoder{ "data": { "collectAt": "{Instant}", "id": "{Text}", "status": "booked", "window": "PM" }}It refuses before anything is sent a connection whose environment is live, or whose credential nothing sets (
recording.refused), and keeps nothing from an answer that doesn't decode or a refusal (recording.failed):$ jadpo package record couriers bookings.create --input '{"postcode": "N1 9GU"}'error: The sandbox's answer to `couriers.bookings.create` doesn't decode as `Booking` reason: Reading it found `variant` on `status`: the operation's types and the provider disagree, so a real call would fault. Nothing was recorded. next: Correct `Booking` against the answer and the provider's documentation, then run it again (agent) rule: recording.failed help: https://jadpo.dev/docs/errors/recording.failedThe choices are in "Recordings" below.
Installing safely
- Every package is verified at install, wherever it came from:
jadpo package addandupdateregenerate the surface from the package's code and refuse one that differs from its committedsurface.jadpo_record. - The lock records each package's version, digest and source:
registry,git(the repository, the tag and the tag's commit),path(a folder in the same repository, such as another Jadpo app) orlink(a live checkout during development, below). A public package can never stand in for a git one.jadpo checkandjadpo buildread the lock and the committed folders only. - Publisher trust. Your own company's packages from git install as any other. A third party's package from git, rather than the registry, is marked unverified in the approval report, and needs a person's acceptance to install. Registry packages carry their verified publisher, and curated ones are marked official.
- Prose is unverified wherever it comes from, such as
///descriptions andGUIDE.md. Nothing safety-relevant relies on it.
jadpo package addfrom a folder (../postcodes) or from a git repository at a release tag (github.com/acme/postcodes@v2.1.0): the package is compiled as the app's own integration would be, the surface its code gives has to be its committedsurface.jadpo_recordbyte for byte (install.surface_mismatch), and only then is it copied intopackages/<name>/<version>/and locked with its version, digest and source.updatemoves one to its newest release and shows what changed for review (Records and manifest).removetakes out one the app no longer uses (install.in_use), andlinkandunlinkare under Developing and publishing a package. The choices are in "How packages are installed and linked" below.$ jadpo package add github.com/acme/postcodes@v2.1.0✓ Added Postcodes.v2 2.1.0 folder packages/postcodes/v2 source git github.com/acme/postcodes tag v2.1.0 surface matches its code✓ package added Postcodes.v2 2.1.0 · 1 function · 2 types · 1 failure→ jadpo package show postcodes shows what it offers$ jadpo package update postcodes✓ Updated Postcodes.v2 from 2.0.0 to 2.1.0 folder packages/postcodes/v2 source git github.com/acme/postcodes tag v2.1.0 surface matches its code Changes since 2.0.0 + type OutwardCode✓ package updated Postcodes.v2 2.1.0 · 1 added→ jadpo check checks the app against 2.1.0
Not built yet: the hosted registry; importers other than openapi;
adding another Jadpo app's OpenAPI document with jadpo package add;
publisher trust; jadpo approval; writing an operation's output type from
the sandbox's answer when it has none; and a HAR capture's operations.
Markdown for agents: /docs/where-packages-come-from.md.Built from Jadpo main at a707efb.