| title | Daski Provider — Minimal Starter |
|---|---|
| status | canonical |
| authority | current repository implementation and approved maintainer workflow |
| owner | repository maintainers |
| effective-date | 2026-10-16 |
| last-verified | 2026-10-16 (documentation placement; no live release verification) |
| supersedes | repository-wide agent files as the home of general repository guidance |
| superseded-by | none |
| sensitivity | public-safe |
| related-repositories | daski-io/provider |
| review-trigger | setup, architecture, interface or workflow change |
Expose one of your existing product operations as a paid service on Daski. This repository supplies the smallest supported provider-side boundary for discovery, standard Exact-EVM payment admission, signed dispatch, replay protection, execution, and terminal evidence. You supply the product integration and operate the deployed provider.
The normal first target is Daski Testnet on Base Sepolia. Mainnet is a separate whitelisted release requested through the Daski Discord.
Pick one of three paths from the product's facts, not from how long a call takes:
| Path | Use it when every paid operation |
|---|---|
| This repository, synchronous mode | Is fixed-price and automated, returns its terminal result within the 50-second synchronous budget, needs no later buyer, asset or operator step, and leaves no mutation ambiguity unresolved when the call ends. |
| This repository, external-job mode | Is fixed-price and automated, and the product already runs the paid work as its own job: it starts promptly, returns or can look up a durable job reference by the order key, is polled until a report that fits the declared result schema, and can prove whether a lost start happened. No later buyer input, cancellation, asset or operator workflow. |
provider-full |
Needs dynamic pricing, provider-owned multi-step workflows, later input or cancellation, assets and owner actions, human or operator review, richer protected data, email, admin, direct A2A, or intentional multi-replica operation. |
Duration alone never selects provider-full: an eight-hour product job that
can be started safely and found again is an external job here. Choosing
provider-full does not supply a missing product guarantee either: if a
lost start cannot be reconciled, that is an upstream integration gap to
close before onboarding in either repository. An existing x402 endpoint does
not make a product compatible by itself; Daski's managed payment is the
purchase (see "How an order reaches your product").
The two starters share the same Daski standard rail. They are alternatives, not layers: do not install one inside the other. This repository is the canonical home of the portable Daski provider agent skill.
| Core owns | Your service owns |
|---|---|
| Discovery, AgentCards, and skill documents | Public service and skill descriptions |
| Gateway signature, audience, quote, request, payment, and evidence checks | Strict product input validation |
| Daski-issued runtime listing catalog and fixed payment bindings | One fixed API or MCP operation per skill |
| PostgreSQL replay/idempotency ledger and terminal results | Product credentials and response mapping |
| Provider terminal attestations and status lookup | Product-specific tests and safe error codes |
| External-job worker: persistence, stable operation keys, leases, polling, restart recovery, running each order with the adapter version that sold it, encrypted job state | For a product job: start, read-only recovery lookup, poll and report mapping, and keeping old adapter versions while their jobs run or their quotes may still be dispatched |
| HTTP limits, rate limits, outbound-network controls, health, and logging | Product dependency readiness and operations |
This is not a buyer SDK, generic API proxy, MCP gateway, payment simulator, or hosting template. Paid calls arrive only through the Daski gateway. Your adapter never decides whether payment is valid and must never accept a buyer-selected URL, HTTP method, MCP server, tool name, or credential.
The offline example and unit tests do not need a database. A running paid provider does. PostgreSQL is the small durable rail ledger that prevents a signed dispatch or admitted chain event from being replayed after a process restart. It stores:
- transaction identity, state, execution lease, terminal result, and operations revision;
- signed readiness quotes that later dispatches bind to;
- admitted public deposit and release evidence;
- shared rate-limit buckets;
- optional external-mutation intent for operations that use the supplier journal; and
- for external jobs, one row per job with its phase, schedule, lease and encrypted input and job reference.
It does not need to become your product database. A synchronous call's request body is not stored; an external job's input is kept, encrypted, only until the job finishes. Removing the ledger would make exactly-once dispatch claims and restart-safe status impossible, so a database-free paid mode is not supported.
Install Node.js 24 and npm, then:
git clone https://github.com/daski-io/provider.git
cd provider
npm ci
npm run try-skill -- dummy echo "hello daski"The command invokes only src/services/dummy in memory. It does not use a
wallet, database, RPC, gateway, payment, or external API. It is a code-path
tour, not a paid-order simulation. npm run try-skill -- synthetic-audit audit tours the external-job example the same way, with a fake clock.
Next read Getting started, copy .env.example,
start the loopback-only PostgreSQL service, and run:
npm run dev:db:up
npm run doctor -- --stage=testnetdoctor is read-only, emits stable check codes, and never prints secret
values; --database adds read-only database diagnostics. A copied
.env.example is intentionally not bootable: Daski supplies a mutually
consistent Testnet runtime bundle plus signer, contract, policy, and evidence
bindings during onboarding.
- The provider publishes your service, skill, closed schemas, fixed atomic-USDC price, and legal/support metadata as a hashable contract.
- Before issuing a payment challenge, the gateway asks the provider for a signed readiness quote. Core validates the request, checks capacity and optional service readiness, and signs the listing's fixed price for that exact request and payer. There is no dynamic pricing.
- The gateway admits payment and sends a short-lived signed dispatch with the quote, exact request, payer, order, audience, and chain evidence.
- Core verifies every binding, including the persisted provider quote, and atomically claims the order in PostgreSQL before the envelope expires.
- Synchronous mode: your adapter maps the skill to one reviewed API
endpoint or MCP tool and returns
completedorfailedwithin the 50-second synchronous budget. External-job mode: core commits the funded order as one queued job and answerssubmitted(or a later state the worker already persisted); the dispatch handler never calls the product. Its worker then calls your adapter'sstart,recoverStartandpollhooks in short, bounded calls until the product reports, for hours if need be, and past the declared fulfillment window as overdue work. - Core validates the result against the listing's admitted result schema, freezes it with its hash, and returns a provider-signed snapshot with a terminal attestation. Exact replays and status reads receive the same durable result, freshly observed and re-signed.
- The buyer reads status and the artifact through the gateway's signed lifecycle call; the artifact is the frozen result itself. Follow-up input, cancellation and support are answered as unsupported.
HTTP call time and fulfillment time are different clocks: every call into this provider is short, while an external job's fulfillment may take hours and is tracked through status reads, never through a request held open. The adapter receives work only after core verified Daski's managed payment and dispatch; Daski does not list arbitrary x402 endpoints, and an adapter never pays an upstream endpoint again for an order Daski already admitted.
Never guess whether an external purchase, provisioning call, or other non-convergent mutation succeeded. In synchronous mode an ambiguity must be resolved within the call; a product job that can be found again by its order key belongs in external-job mode, where core journals the start and reconciles it after any restart.
src/core/ service-neutral Daski and security boundary
src/core/externalJobs/ external-job admission, worker and job state
src/core/diagnostics/ read-only database checks for the doctor
src/services/dummy/ Testnet-only synchronous reference service
src/services/synthetic-audit/ Testnet-only external-job example (not composed)
src/providerServices.ts installed service composition
src/installRuntimeBundle.ts verified Daski-assisted catalog importer
src/upgradeCutover.ts pre-R1 stop-and-drain cutover inventory
src/activateAsyncAdmission.ts async-admission switch (operator command)
src/doctorDatabase.ts doctor's read-only database mode
src/core/gatewayRegistration/ runtime commitment and catalog primitives
test/ core and cross-service tests
docs/ installation, integration, and onboarding
.agents/skills/daski-provider/ portable agent skill and routed references
scripts/ local diagnostics and verification
compose.yaml loopback-only development PostgreSQL
Dockerfile hosting-neutral production image
src/core/owns Daski protocol, identity, payment/evidence verification, the minimal PostgreSQL rail ledger, security, discovery, and HTTP runtime.src/core/externalJobs/owns external-job admission, the durable worker (leases, phases, scheduling, recovery) and encrypted job state; it is the only code that calls an external-job adapter (calls.ts).src/services/<slug>/owns one service's manifest, validation, adapter, product configuration/client, docs, and co-located tests.src/providerServices.tsis the single installed-service composition.- Paid-skill launch policy is derived from the installed manifests.
src/installRuntimeBundle.tsverifies and atomically promotes the Daski-issued runtime catalog; this starter does not self-register.
Core must never import from services. Services must never import sibling services. Product behavior must remain behind a typed service adapter. The architecture gate enforces these directions and rejects full-starter surfaces.
Service-specific tests belong in src/services/<slug>/tests/. test/
contains only core and cross-service tests.
Start with Integrating an existing product, then follow Adding a service. In short:
- choose the path: synchronous, external job, or
provider-full; - copy
src/services/dummy(synchronous) orsrc/services/synthetic-audit(external job) tosrc/services/<your-slug>; - replace its manifest, schema, validation, adapter, docs, and tests;
- map each skill to a hard-coded, reviewed product operation;
- install it in
src/providerServices.ts; - coordinate the published contract and fixed price with Daski;
- install the exact Daski-issued runtime bundle;
- remove
dummy; and - complete an end-to-end Testnet purchase before requesting Mainnet review.
After the database and reviewed Testnet configuration are ready, use npm run dev
for the source watcher, or npm run build followed by npm start for built output.
npm run dev:db:stop stops the development database while preserving its named
volume. Unit tests need no live RPC, gateway or supplier; database checks require
an explicitly selected disposable database. Keep runtime keys and issued artifacts
outside Git and ordinary support messages.
- Every skill has a reviewed fixed atomic-USDC price. Core answers the
gateway's signed readiness quote with that fixed amount; there is no
dynamic pricing. An optional read-only
requestReadinesshook may decline a request before payment but cannot change the price or start work. - A service whose orders pay a supplier reports the supplier's current price
as
supplierCostfromrequestReadiness; core records it with the quote, withSUPPLIER_PRICE_TOLERANCE_PERCENT, as the order's immutable spend ceiling. Check every planned spend withcheckSupplierSpend(context.supplierCostCeiling, planned)before making it; when it is refused, spend nothing and fail withsupplier_cost_ceiling_exceeded. - Validate input both at the rail schema and in the adapter. Reject unknown fields and apply explicit size/range limits.
- A synchronous adapter returns only
completedorfailed, honors itsAbortSignaland finishes within the 50-second synchronous budget. An external-job adapter (mode: "external-job") returns only the typed outcome unions; any throw, abort, timeout or invalid return fromstartis ambiguous and reconciled withrecoverStart(read-only) under the original key, never retried blindly. Only core schedules a start. resultSchemadescribes the complete completedServiceResult; core validates results against the admitted schema before freezing them. Failure evidence is only a code listed in the module'sfailureCodes, one list for every installed adapter version (keep a code while a retained version can return it).- Core records the external-job adapter version selling each quote and runs
the order with exactly that version, even after new sales move on; keep
earlier versions in
previousExternalJobAdapterswhile their jobs run or their quotes may still be dispatched. A quote without one (a synchronous sale, or one from before migration 010) runs with the current adapter. - A terminal task's state, result, result hash and completion time are frozen in one write and never change; legacy rows are never reshaped.
- Lifecycle calls verify the gateway's
ProviderLifecycleGrantV1before interpreting the authorization. A read capability authorizes onlystatusandartifact;input,cancelandsupportanswer exactly409 unsupported_actionwith no effect. Lifecycle reads never call the adapter, write, or answer 500. - PostgreSQL is mandatory for paid runtime replay/idempotency, evidence, rate-limit, and terminal-result state. Do not replace it with memory state.
- Dispatch claims, financial/evidence state, and external mutation intent use conditional writes, transactions, locks, and stable idempotency keys.
- A dispatch JSON
errormeans "definitely not admitted". Once a claim exists or its commit is uncertain, answer the signed snapshot or the empty-body 503; only a verified status query with no claim gets theoutcome_policy404. Never refuse a funded dispatch for capacity or paused sales, or for quote age within the protocol's first-dispatch bound (a new claim from a dispatch issued more than 30 days after itsQuoteV1expired is refusedquote_window), and never cache a signed response. A quote is kept until itsvalidBeforeplus 31 days, and forever once a claim names it. - For a non-convergent product mutation, journal intent before the call and
reconcile authoritative product state after ambiguity. If reconciliation
cannot complete within the synchronous call, run the product's own job in
external-job mode (core journals the start and reconciles it by the stable
key) or use
provider-fullfor provider-owned workflows; never guess or blindly retry. An adapter never journals its own external-job start. - Daski's verified payment is the purchase. An external-job
startuses a provider-owned creation operation tied to the verified order; never pay a public x402 or other paid endpoint again, and never accept a caller-asserted "already paid" flag. - External-job input, job references and invalid-result candidates are
stored only encrypted (
PROVIDER_DATA_ENCRYPTION_KEY). Never log or publish a bearer job URL, and keep every key id an unfinished job names. - A buyer-authorized payer comes only from the verified dispatch. Request fields, order ids, artifact ids, and caller metadata are not credentials.
- Use
boundedFetch,pinnedHttpRequest, orreviewedEndpointfor product HTTP. Do not callfetchdirectly from a service. - Never log request/product payloads, credentials, private keys, or signed authorization material. Public errors are stable and generic.
- Applied migrations are immutable and checksummed. New migrations are
append-only, listed in the static-gate allowlist with the tables they
create, and every new table needs a reviewed runtime-role grant and the
statement-level
provider_runtime_epoch_barrier()trigger. A later release whose migration adds a table or changes a guarded write path must raiseRUNTIME_SCHEMA_EPOCHand theprovider_schema_epochrow together; a purely additive change no older runtime path can misuse (an index, say) may keep it (docs/releasing.md). - The async-admission switch changes only through
npm run daski:async-admission; migrations, the importer and boot never enable it. - Treat published contract hashes and fixed-price changes as coordinated Daski onboarding changes. Request and install a new signed runtime bundle; never edit catalog rows or issued artifacts.
- The dummy service is forbidden on Base Mainnet and must be removed before Mainnet review.
npm run typecheck
npm run typecheck:test
npm run lint
npm run lint:architecture
npm run docs:check
npm run skill:validate
npm run test:run
npm run test:coverage
npm run test:critical-coverage
npm run test:mainnet-readiness
npm run security:audit
npm run buildUnit gates need no live database, chain, gateway, or product. Database smoke checks are intentionally separate and must target only a disposable database. See scripts/README.md.
The Dockerfile is the canonical production artifact; the upstream repository
does not prescribe a hosting vendor. A deployment needs a
stable public HTTPS BASE_URL, PostgreSQL 16+, runtime-injected secrets,
outbound Base RPC/product access, and one active application replica. Route
traffic only when /health/ready passes; /health/live proves only that the
process exists. /health/ready passes while funded orders' status and
artifact reads can be served; its admission part says whether new sales are
taken and its servicing part reports owed work
(Troubleshooting).
/health/admission answers 200 only while new sales are open: alert on it,
and gate a release's promotion on it when its sales were already open.
This minimal starter does not self-register or submit chain transactions. Daski onboarding supplies a reviewed runtime bundle and matching global policy. After configuring the supplied values, install the bundle once per service:
npm run daski:install-runtime -- --file /secure/path/runtime-bundle.jsonUpgrading a deployment that ran a pre-R1 image is a stop-and-drain cutover
(npm run daski:cutover), described in
docs/onboarding.md.
External jobs additionally need PROVIDER_DATA_ENCRYPTION_KEY and the
operator's explicit npm run daski:async-admission -- --activate; no
migration, import or boot turns that switch on. Later compatible upgrades
keep the old listing versions, URLs, signing and encryption keys and adapter
versions that unfinished orders need. This release requires the companion
gateway revision described in
docs/releasing.md; the tested
daski-io/gateway commit is recorded in
test/fixtures/provider-wire.source.json.
The importer verifies signatures, domains, provider intent, the exact local skill-contract hashes and prices, runtime commitments, and splitter provenance before an atomic append-only catalog promotion. Keep the file out of Git and ordinary support messages.
Develop locally while pointing the provider and gateway at reviewed Base Sepolia contracts. A fully private local payment topology also needs contracts, a facilitator, signers, and newly issued artifacts and is protocol development, not the provider quickstart.
Synchronous executions retain a persisted owner and expiring lease. A restart
or overlapping compatible process leaves a live call running; only an expired
lease fails with provider_execution_abandoned. Synchronous calls are never
resumed. External-job workers recover due and abandoned jobs from their stored
phase without resubmitting work.
Mainnet review also requires canonical contracts, separate runtime and migration database roles, verified TLS/proxy posture, removal of dummy, successful Testnet review and explicit Daski whitelisting.
- Getting started
- Integrating an existing API or MCP product
- Adding a service
- Configuration
- Testnet and Mainnet onboarding
- Troubleshooting
- Architecture
- Protocol cheatsheet
- Agent skill
- Releasing and compatibility
- Security model
- Scripts
Start with this README and the portable
.agents/skills/daski-provider/SKILL.md. Repository documentation is
authoritative after checkout. The separately packaged skill also includes a
zero-context Daski primer plus focused references for starter selection,
product mapping, external jobs, full-provider work, and onboarding, so it
remains useful before a repository is cloned. Install it with
npx skills add https://github.com/daski-io/provider --skill daski-provider
(details).
Fork this repository and retain it as an upstream remote:
git remote add upstream https://github.com/daski-io/provider.git
git fetch upstream
git merge upstream/developKeep product code inside its service folder and review protocol/migration
changes before merging. Never edit an applied migration. Read the release's
upgrade notes before deploying it: the first upgrade from a pre-R1 image is a
stop-and-drain cutover, while later compatible releases may overlap a
running one. Replace the security contact in SECURITY.md with a private
channel operated by your organization.
- Decide which path the operation fits: synchronous mode, external-job
mode, or
provider-full. Stop and recommendprovider-fullwhen a required capability is full-only. - Inspect the closest types, implementation, docs, and tests.
- Make the smallest cohesive change; update contract, validation, adapter, docs, composition, catalog expectations, and tests together.
- Keep API/MCP targets in a fixed allowlist. Never pass through a buyer URL, HTTP method, MCP server, tool name, credential, or arbitrary headers.
- Run targeted tests, then all relevant repository gates.
- Scan the diff for secrets, personal data, product account details, raw product output, and organization-specific leakage.
- Never weaken a fail-closed check to make a fixture or deployment pass.
Keep files near 250 lines where practical and split by cohesive responsibility.
Propose changes against develop. Follow releasing and compatibility
for a provider fork. The starter itself is not a deployed Daski service;
merging a starter change does not deploy one. Coordinate protocol and fixture
changes with the gateway and the other starter. Keep organization-specific
policy and private operational data outside the public repository.
MIT. See LICENSE.