@dak-os/marketplace
The marketplace primitive of the Dak Stack — the MarketplaceListing format (a promoted,
eval-gated skill packaged for cross-tenant sharing), the listing JSON Schema (which ships with the
package), and the MarketplaceRegistry contract: publish / list / get / resolveForInstall
over a git-portable directory of JSON listings. It depends only on @dak-os/skills (whose
Skill / SkillStatus and eval PromotionDecision types it reuses) and ajv.
The first package of Stage 8 (the skills/agents marketplace — the capability
flywheel), recorded in ADR-0022. It copies
@dak-os/skills's shape — package = format + loader + contract; the eval GATE stays a
consumer. MIT-licensed and independently adoptable.
What a listing is
A MarketplaceListing is the public face of a skill on the marketplace — its identity, how it's found, who shared it, and (the load-bearing part) why it's trustworthy:
| Field | What it is |
|---|---|
id / version / name / description / status / triggers |
the listed skill's identity + discovery fields — the same types as a Skill's fields (reused, never duplicated) |
publisher / license / category |
who shared it, under what license, and a coarse class for discovery |
provenance |
the eval gate's promotion decision (@dak-os/skills/eval's PromotionDecision: baseline/candidate scores, delta, margin, regressions), plus the sourceTraces the skill was distilled from and the evalSuiteId that judged it |
payload |
the installable content (ADR-0024): the SKILL.md playbook body + the travelling held-out evals suite (@dak-os/skills/eval's EvalCase[], non-empty). It travels with the listing because the marketplace is the only shared cross-tenant surface — an installer never reads the publisher's skills/ dir or spine. Deliberately no fixtures/artifacts: a consumer re-gates with its own outputs, never by replaying the publisher's |
stats |
usage statistics — a placeholder in inc 1 (installs); take-rate accounting (Stage 8 inc 4) extends it |
The trust floor
A listing is well-formed only if its provenance carries a PASSING gate —
provenance.decision.promotemust betrue— and it can be re-verified:payload.evalsmust be non-empty (ADR-0024).
You may publish a skill you proved beat its baseline on a held-out eval suite, never a skill you merely
wrote. validateListing enforces this both via the bundled schema (which pins decision.promote to
const true and payload.evals to minItems: 1) and in code (defense in depth), and the registry
checks it at the write boundary AND re-checks on every read — so a hand-edited or tampered file fails
loud rather than flowing on as trusted.
This is the publisher's obligation. The installer's trust model is separate and stricter: installing a foreign skill re-gates it locally (never trust a foreign score) — Stage 8 inc 3, ADR-0024. This package guarantees a listing is structurally trustworthy; it does not ask you to believe its number.
What's here
| Subpath | What it is |
|---|---|
@dak-os/marketplace |
The barrel — the whole surface below. |
@dak-os/marketplace/listing |
The MarketplaceListing format + MarketplaceProvenance / MarketplaceStats, validateListing / isWellFormedListing (schema + trust-floor checks), listingFromSkill (build a listing from a promoted skill + its decision), and InvalidListingError. |
@dak-os/marketplace/registry |
The MarketplaceRegistry contract (publish / list / get / resolveForInstall) and LocalFileMarketplaceRegistry, the git-portable reference impl over a marketplace/ directory of <id>.json listings. |
The canonical listing.schema.json ships under schema/ and is loaded by the format itself
(import.meta.dir) — the format is the package's, not the consumer's content dir.
The seam: the package is the contract, the gate is a consumer
Like @dak-os/skills (ADR-0015), @dak-os/marketplace carries the vocabulary (the listing
format, the registry contract) and the mechanics a host needs at the ends — publish (write a listing
out) and resolveForInstall (read one in for the install path). The eval gate that produces a
listing's provenance.decision stays a consumer in the app (src/eval/*), wired to the host's model
and event store — it is the mechanism, the package is the stable contract.
The marketplace is the one cross-tenant surface
The reference registry is backed by a plain marketplace/ directory of JSON files, committed to git.
This is deliberate: the marketplace is the one surface that crosses the tenant boundary, so it must
not live on any tenant's spine (tenant-isolated by construction — Stage 7). A directory of files is a
surface every tenant can read and a publisher can PR into, with no boundary to cross and no spine to
breach.
Install
bun add @dak-os/marketplace # or: npm install @dak-os/marketplace
Until the packages are on npm (publishing is tracked in advisor-plans/002), consume them from the
monorepo: clone https://github.com/twofoldtech-dakota/dak and depend on packages/marketplace via
file:/workspace resolution.
Use
import { LocalFileMarketplaceRegistry } from "@dak-os/marketplace";
import { listingFromSkill } from "@dak-os/marketplace/listing";
const registry = new LocalFileMarketplaceRegistry("marketplace");
// Publish: package a promoted skill + the gate decision that promoted it. Rejected unless the gate passed.
const listing = listingFromSkill(skill, promotionDecision, {
publisher: "dak",
license: "MIT",
category: "writing",
evalSuiteId: "doc-drafting-suite@3",
});
registry.publish(listing);
// Discover + resolve for install (the install path then re-gates locally — never trust a foreign score).
const all = registry.list();
const chosen = registry.resolveForInstall("doc-drafting");
No build step — the package exports its .ts source directly and runs under Bun (and resolves under
tsc with moduleResolution: bundler). See ../README.md for the conventions every Dak
Stack package follows.
Test
bun test
The tests are standalone — they build synthetic listings in a temp dir and exercise the format, the
trust floor (a non-passing gate is rejected), the registry round-trip (publish → list → get),
republish-replaces, read-time re-validation (a tampered file fails loud), and id path-traversal guarding,
against the bundled schema — proving @dak-os/marketplace needs no Dak content or network.