Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions reference-implementations/dacs-directory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ discovery layer (DACS-1 §6.3.6 catalog API), with a browsable directory UI and

Agents do NOT need to register to appear here: the indexer **crawls the chain**
(see *Discovery — three channels* below) and picks up current structured listings and
the pinned SDK's legacy artifacts through program-name and content-shape detection. Registration adds a display
the pinned SDK's explicit legacy read profile through program-name and content-shape detection. Current
listings must pass the pinned SDK's normative `isListing()` gate before admission. Registration adds a display
name and (when owner-signed) the "owner-registered" badge — it is never a gate.

Live thesis: a Web2 marketplace *asks you to trust its database*. This directory is a
Expand Down Expand Up @@ -175,6 +176,13 @@ and response failures retain bounded retries. `STORAGE_NOT_FOUND` is operational
diagnostic evidence only: under the current Demos mapping it is never authoritative
DACS-5 absence evidence and cannot satisfy BB-8.

The same status response exposes `listingRejectionDiagnostics` with scope
`listing-admission`. It reports stable public-safe classes for normative shape,
verification-method, signature, identity-presentation, owner/seller binding and
declared-hash failures. For example, string-valued deliverable verification methods
are excluded as `VERIFICATION_METHOD_INVALID`; the Directory never aliases them to a
registered structured verification-method variant.

## Discovery — three channels

1. **Registration** (`/register` UI or `POST /api/dacs/register`): bounded pointer sets,
Expand Down Expand Up @@ -211,8 +219,11 @@ The Next app and the indexer speak to the node over **plain HTTP** (storage read
unauthenticated GETs; `gcr_routine` uses hand-rolled timestamp-bound auth headers signed
with the SDK's pure ed25519). demosdk is NOT a runtime dependency — its dependency tree
(rubic bridge → pancakeswap/cetus/…) has unresolvable optionals in consumer installs and
is bundler-hostile. The SDK's pure barrel does all cryptography, on both server and
client (browser: @noble-shimmed `node:crypto`, base64url-patched Buffer).
is bundler-hostile. SDK verification names resolve through one compatibility seam because
the current top-level SDK barrel statically re-exports those optional rail modules; replace
that seam when the SDK publishes a browser-safe verification subpath. The same verification
code runs on server and client (browser: @noble-shimmed `node:crypto`, a narrow `node:util`
shim over JSON-owned values, and base64url-patched Buffer).

## Honest limitations (MVP)

Expand Down Expand Up @@ -247,8 +258,9 @@ client (browser: @noble-shimmed `node:crypto`, base64url-patched Buffer).

`exercises-spec`: DACS-1 §6.3.4 current Listing publication and dual-profile reading,
§6.3.5 well-known generation/crawling, and §6.3.6 catalog discovery. Current artifacts
use directory-native, current-contract evidence-graph validation; the pinned SDK verifier
is retained only for labelled legacy artifacts. DACS-2 tier derivation fails closed on
pass the pinned SDK's normative Listing and component-signature APIs; current-contract
evidence graphs use directory-native validation, while the SDK bundle verifier is retained
for labelled legacy artifacts. DACS-2 tier derivation fails closed on
unresolved recipe/evidence/freshness, and DACS-5 derivation includes ratings, volume,
settlement uniqueness, anchor-time windowing, and deterministic receipts. Catalog
computations remain advisory and independently reproducible from their refs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,11 @@ export async function POST(req: NextRequest) {
? { kind: "storage-program", accessModel: "public" }
: deliverableKind === "entitlement"
? { kind: "entitlement", durationSec: 2_592_000, renewable: false }
: { kind: "attested-payload", payloadFormat: "application/json" };
: {
kind: "attested-payload",
payloadFormat: "application/json",
verificationMethod: { kind: "self-signed" },
};
const auctionDeadline = identityPresentedAt + 7 * 24 * 60 * 60 * 1000;
const negotiationKind = negotiationPhaseForPricing(pricingKind);
const negotiationStep = negotiationKind === "negotiate-rfq"
Expand Down Expand Up @@ -277,7 +281,7 @@ export async function POST(req: NextRequest) {
// Listing versions are immutable. Recover the already-signed artifact and
// continue registration; never overwrite it with newly generated bytes.
anchorAddress = resolution.address;
publishedListing = verified.listing as Record<string, unknown>;
publishedListing = verified.listing as unknown as Record<string, unknown>;
publishedHash = verified.contentHash;
} else {
const nonce = await accountNonce(hex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export async function GET(req: NextRequest) {
found: !!anchored,
valid,
ownedByClaim: valid,
title: verified?.listing.name ?? null,
title: verified?.profile === "dacs-v0.1"
? verified.listing.offering.title
: verified?.listing.name ?? null,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
* known deals. Registration becomes "confirm what we found", not data entry.
*/
import { NextRequest, NextResponse } from "next/server";
import { isListing } from "@kynesyslabs/dacs/artifacts";
import { stripSignature } from "@kynesyslabs/dacs/canonical";
import { parseCciRecord } from "@kynesyslabs/dacs/identity";
import { readAnchor } from "@/src/catalog/chain";
import { gcrGetIdentities } from "@/src/catalog/gcr";
import { verifyListing } from "@/src/catalog/listingVerification";
import { loadScanState } from "@/src/catalog/store";

export async function GET(req: NextRequest) {
Expand All @@ -26,9 +25,14 @@ export async function GET(req: NextRequest) {
for (const [address, o] of Object.entries(state.listings)) {
if (o.toLowerCase() !== owner.toLowerCase()) continue;
const raw = await readAnchor(address);
const scope = raw ? stripSignature(raw) : null;
if (scope && isListing(scope)) {
listings.push({ address, title: (scope as { name?: string }).name ?? address });
const verified = raw ? await verifyListing(raw) : null;
if (verified) {
listings.push({
address,
title: verified.profile === "dacs-v0.1"
? verified.listing.offering.title
: verified.listing.name,
});
}
}

Expand Down
5 changes: 5 additions & 0 deletions reference-implementations/dacs-directory/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ const nextConfig = {
resource.request = new URL("./src/shims/node-crypto.ts", import.meta.url).pathname;
}),
);
config.plugins.push(
new webpack.NormalModuleReplacementPlugin(/^node:util$/, (resource) => {
resource.request = new URL("./src/shims/node-util.ts", import.meta.url).pathname;
}),
);
config.plugins.push(
new webpack.ProvidePlugin({ Buffer: ["buffer", "Buffer"] }),
);
Expand Down
21 changes: 0 additions & 21 deletions reference-implementations/dacs-directory/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions reference-implementations/dacs-directory/scripts/setup-sdk.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Vendors + builds the dacs-sdk (not yet on npm) and installs the app.
set -euo pipefail
cd "$(dirname "$0")/.."
SDK_REV="44d8ff2a07df8c951b94619d20b957b4bb5ce140"
SDK_REV="2d53f03778189b8f36573720e68d8743a94e4f2b"

# Railway's GitHub integration can check out this repository, but it does not
# pass its credentials through to nested private-repository clones. Supply a
Expand Down Expand Up @@ -31,10 +31,10 @@ else
git_with_sdk_auth clone --filter=blob:none https://github.com/DACS-Agent-commerce/dacs-sdk.git vendor/dacs-sdk
fi
(cd vendor/dacs-sdk && git_with_sdk_auth fetch --depth 1 origin "$SDK_REV" && git_with_sdk_auth checkout --detach "$SDK_REV")
(cd vendor/dacs-sdk && npm install --no-audit --no-fund && npm run build)
(cd vendor/dacs-sdk && npm ci --no-audit --no-fund && npm run build)
fi
if [ "${DACS_SKIP_APP_INSTALL:-0}" != "1" ]; then
npm install --no-audit --no-fund
npm ci --no-audit --no-fund
fi
# Seed the (gitignored, runtime-mutated) registrations file from the example
# so a fresh clone has demo data without the file churning in git.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { contentHash, stripSignature } from "@kynesyslabs/dacs/canonical";
import type { AttestationBundle } from "@kynesyslabs/dacs/artifacts";
import type { BundleVerification } from "../../vendor/dacs-sdk/dist/agent/verifyBundleCore.js";
import {
isLegacyMvpAttestationBundle,
} from "@kynesyslabs/dacs/artifacts";
import type { BundleVerification } from "@kynesyslabs/dacs";

import { bundleSignerPolicy, demosSigningIdentity } from "./bundleSignerPolicy.js";
import { verifyListing } from "./listingVerification.js";
Expand Down Expand Up @@ -84,11 +86,11 @@ export function bundleMatchesRegisteredAnchor(
* they must never be allowed to reassign somebody else's bundle/reputation.
*/
export function bundleMatchesRegisteredDeal(
bundle: AttestationBundle | undefined,
bundle: BundleVerification["bundle"],
deal: RegisteredDeal,
catalogSeller: string,
): boolean {
if (!bundle || bundle.jobId !== deal.jobId) return false;
if (!isLegacyMvpAttestationBundle(bundle) || bundle.jobId !== deal.jobId) return false;
const buyers = bundle.parties.filter((p) => p.role === "buyer");
const sellers = bundle.parties.filter((p) => p.role === "seller");
return buyers.length === 1 && sellers.length === 1 &&
Expand Down Expand Up @@ -129,7 +131,8 @@ function expectedArtifacts(verification: BundleVerification): ExpectedArtifact[]
// The pinned compatibility SDK does not resolve or report amendments/ratings.
// A nonempty set must fail closed here instead of receiving a partial "strict"
// verdict. The current-profile evidence graph resolves ratingRefs separately.
if (!bundle || bundle.agreementRef.kind !== "dacs-3-agreement" ||
if (!isLegacyMvpAttestationBundle(bundle) ||
!bundle.agreementRef || bundle.agreementRef.kind !== "dacs-3-agreement" ||
bundle.settlementEvidence.some((ref) => ref.kind !== "dacs-4-evidence") ||
bundle.vetRecords.some((ref) => ref.kind !== "dacs-2-verifyresult") ||
[extended?.amendments, extended?.ratingRefs].some((refs) => refs !== undefined &&
Expand Down Expand Up @@ -285,7 +288,7 @@ export function verifiedListingTerms(
}

export function bundleCategory(
bundle: AttestationBundle | undefined,
bundle: { listingRef: { listingId: string } } | undefined,
categoriesByListing: Map<string, string>,
): string | undefined {
return bundle ? categoriesByListing.get(String(bundle.listingRef.listingId)) : undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ export const catalogStatusSchema = {
type: "object",
required: ["scope", "total", "byCode", "query", "returned", "hasMore", "items"],
properties: {
scope: { const: "listing-registration-binding" },
scope: { const: "listing-admission" },
total: { type: "integer", minimum: 0 },
byCode: { type: "object", additionalProperties: { type: "integer", minimum: 0 } },
query: {
Expand Down
37 changes: 22 additions & 15 deletions reference-implementations/dacs-directory/src/catalog/indexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,11 @@
import { ed25519Verify, publicKeyFromRaw } from "@kynesyslabs/dacs/crypto";
import { contentHash } from "@kynesyslabs/dacs/canonical";
import { parseCciRecord } from "@kynesyslabs/dacs/identity";
// verifyBundleCore has no pure subpath export (dacs-sdk#14) — vendor path.
import { verifyBundleCore } from "../../vendor/dacs-sdk/dist/agent/verifyBundleCore.js";
// The SDK doesn't export sessionAnchorName from its public barrel
// (dacs-sdk#14) — reach into the vendored build.
import { sessionAnchorName } from "../../vendor/dacs-sdk/dist/agent/runSessionCore.js";
import { isLegacyMvpAttestationBundle } from "@kynesyslabs/dacs/artifacts";
import { verifyBundleCore } from "@kynesyslabs/dacs";
import { deriveAnchorAddress, readAnchor, readAnchorRecord } from "./chain.js";
import { gcrGetIdentities } from "./gcr.js";
import { findValidListingRevocation, ownerClaim, verifyListing } from "./listingVerification.js";
import { findValidListingRevocation, ownerClaim, verifyListingResult } from "./listingVerification.js";
import { canonicalDemosAgentClaim } from "./claimRef.js";
import { resolveDemosPrimaryClaimKey } from "./primaryClaimKey.js";
import { listingPresentation } from "./listingMetadata.js";
Expand All @@ -48,6 +45,7 @@ import {
verifyBundleBinding,
} from "./bundleBinding.js";
import { safePublicEndpoint } from "./publicEndpoint.js";
import { legacySessionAnchorName } from "./legacySessionAnchorName.js";
import { deriveIdentityTier, type ResolveRecipe } from "./identityVerification.js";
import {
bundleMatchesRegisteredDeal,
Expand Down Expand Up @@ -121,7 +119,7 @@ export async function indexRegistration(
const explorerFor = (chainType: string, address: string): string | undefined =>
chainType === "evm" ? `https://etherscan.io/address/${address}` :
chainType === "solana" ? `https://solscan.io/account/${address}` : undefined;
cci = record.claims.map((c) => c.kind === "web2"
cci = record.claims.filter((c) => c.kind === "web2" || c.kind === "wallet").map((c) => c.kind === "web2"
? { kind: c.kind, platform: c.platform, handle: c.handle, ref: c.ref,
proofUrl: proofFor(c.platform, c.handle), linkUrl: profileFor(c.platform, c.handle) }
: { kind: c.kind, platform: c.chainType, handle: c.address, ref: c.ref,
Expand All @@ -139,8 +137,12 @@ export async function indexRegistration(
for (const anchor of reg.listingAnchors) {
const anchored = await readAnchorRecord(anchor);
if (!anchored) continue;
const verified = await verifyListing(anchored.data);
if (!verified) continue;
const verification = await verifyListingResult(anchored.data);
if (!verification.ok) {
recordListingRejection(anchor, reg.primaryClaim, verification.code);
continue;
}
const verified = verification.value;
const { scope } = verified;
const bindingRejection = listingBindingRejection(
verified.sellerClaim,
Expand All @@ -151,9 +153,12 @@ export async function indexRegistration(
recordListingRejection(anchor, reg.primaryClaim, bindingRejection);
continue;
}
clearListingRejection(anchor, reg.primaryClaim);
const declaredHash = reg.listingContentHashes?.[anchor]?.replace(/^sha256-/, "").toLowerCase();
if (declaredHash && declaredHash !== verified.contentHash) continue;
if (declaredHash && declaredHash !== verified.contentHash) {
recordListingRejection(anchor, reg.primaryClaim, "DECLARED_CONTENT_HASH_MISMATCH");
continue;
}
clearListingRejection(anchor, reg.primaryClaim);
const listingId = typeof scope.listingId === "string" ? scope.listingId
: typeof scope.serviceId === "string" ? scope.serviceId : "";
if (!listingId) continue;
Expand Down Expand Up @@ -369,9 +374,9 @@ export async function indexRegistration(
return raw;
},
resolveRef: async (kind, jobId) => {
const name = kind === "dacs-3-agreement" ? sessionAnchorName.agreement(jobId)
: kind === "dacs-4-evidence" ? sessionAnchorName.evidence(jobId)
: kind === "dacs-2-verifyresult" ? sessionAnchorName.vet(jobId) : null;
const name = kind === "dacs-3-agreement" ? legacySessionAnchorName.agreement(jobId)
: kind === "dacs-4-evidence" ? legacySessionAnchorName.evidence(jobId)
: kind === "dacs-2-verifyresult" ? legacySessionAnchorName.vet(jobId) : null;
if (!name) return null;
const address = findProgramAddress(deal.owners.buyer, name) ?? deriveAnchorAddress(deal.owners.buyer, name);
const raw = await readAnchor(address);
Expand All @@ -382,7 +387,9 @@ export async function indexRegistration(
(await resolveDemosPrimaryClaimKey(claim, "ed25519"))?.publicKey ?? null,
verify,
}).catch(() => null);
const bundle = verification?.bundle;
const bundle = verification && isLegacyMvpAttestationBundle(verification.bundle)
? verification.bundle
: undefined;
const signaturesOk = verification ? hasRequiredBundleSignatures(
verification,
rawBundle,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Historical SDK-MVP program names used only while reading explicitly
* labelled legacy bundles. These strings are not a current normative SDK API.
*/
export const legacySessionAnchorName = {
agreement: (jobId: string): string => `dacs3:agreement:${jobId}`,
evidence: (jobId: string): string => `dacs4:evidence:${jobId}`,
vet: (jobId: string): string => `dacs2:verifyrecord:${jobId}`,
};
Loading