diff --git a/reference-implementations/dacs-directory/src/catalog/bundlePolicy.ts b/reference-implementations/dacs-directory/src/catalog/bundlePolicy.ts index 2f1fb58..9a8ce1d 100644 --- a/reference-implementations/dacs-directory/src/catalog/bundlePolicy.ts +++ b/reference-implementations/dacs-directory/src/catalog/bundlePolicy.ts @@ -7,6 +7,10 @@ import { bundleSignerPolicy, demosSigningIdentity } from "./bundleSignerPolicy.j import { verifyListing } from "./listingVerification.js"; import type { DealRecord, RegisteredDeal } from "./types.js"; +// PAYEE-BOUND COUPLING (issue #17 F2): site (c) of 3 (mirror of evidenceGraph.ts SEPARATORS). +// No `dacs-payee-bound-agreement:v1:` domain yet. When payee-bound support lands, add it here +// AND in evidenceGraph.ts, alongside shapeOk's discriminator and isNeutralCancellation's +// commit-kind. §8.5.1: SELECT the domain from the required discriminator — never strip-and-retry. const SEPARATORS: Record = { "dacs-3-agreement": "dacs-agreement:v1:", "dacs-4-evidence": "dacs-evidence:v1:", diff --git a/reference-implementations/dacs-directory/src/catalog/evidenceGraph.ts b/reference-implementations/dacs-directory/src/catalog/evidenceGraph.ts index 061ff19..db48d12 100644 --- a/reference-implementations/dacs-directory/src/catalog/evidenceGraph.ts +++ b/reference-implementations/dacs-directory/src/catalog/evidenceGraph.ts @@ -38,6 +38,10 @@ const priceTermOk = (value: unknown): boolean => { return Boolean(price && /^(?:0|[1-9]\d*)(?:\.\d*[1-9])?$/.test(amount) && /[1-9]/.test(amount) && typeof price.currency === "string" && price.currency.length > 0 && price.currency.length <= 64); }; +// PAYEE-BOUND COUPLING (issue #17 F2): site (c) of 3. No `dacs-payee-bound-agreement:v1:` +// domain yet. When payee-bound support lands, add it here AND in bundlePolicy.ts SEPARATORS, +// alongside shapeOk's discriminator (above) and isNeutralCancellation's commit-kind +// (reputation.ts). §8.5.1: SELECT the domain from the required discriminator — never strip-and-retry. const SEPARATORS: Record, string> = { agreement: "dacs-agreement:v1:", evidence: "dacs-evidence:v1:", "verify-result": "dacs-verifyresult:v1:", composite: "dacs-composite:v1:", rating: "dacs-rating:v1:", listing: "dacs-listing:v1:", bundle: "dacs-bundle:v1:", @@ -87,6 +91,12 @@ export function isCurrentRef(value: unknown): value is CurrentRef { return Boolean(anchor && anchor.kind === "storage-program" && typeof anchor.locator === "string" && /^stor-[0-9a-f]{40}$/.test(anchor.locator) && /^[0-9a-f]{64}$/.test(normalizedHash(ref?.contentHash))); } function shapeOk(raw: Record, kind: ArtifactKind): boolean { + // PAYEE-BOUND COUPLING (issue #17 F2): site (a) of 3. Today this accepts only + // `agreementVersion === "1"`, so a payee-bound agreement ref (#236) fails shape → + // bundle never refsVerified → excluded (fail-closed, CORE §11.1.2). When payee-bound + // support lands, this discriminator, isNeutralCancellation's commit-kind (reputation.ts + // ~L35), and the SEPARATORS domain (below ~L41 + bundlePolicy.ts ~L10) MUST move together + // — accept exactly-one of agreementVersion / payeeBoundAgreementVersion (§8.5.1), never both. if (kind === "agreement") return raw.agreementVersion === "1" && typeof raw.jobId === "string" && rec(raw.listingRef) !== null && arr(raw.parties).length >= 2 && priceTermOk(rec(raw.terms)?.price); if (kind === "evidence") return raw.evidenceVersion === "1" && typeof raw.jobId === "string" && typeof raw.phase === "string" && (raw.outcome === "success" || raw.outcome === "failure") && typeof raw.observedAt === "number" && (raw.amendmentRefs === undefined || Array.isArray(raw.amendmentRefs)) && (raw.attestationRef === undefined || isCurrentRef(raw.attestationRef)); if (kind === "verify-result") return raw.resultVersion === "1" && typeof raw.scheme === "string" && typeof raw.identifier === "string" && typeof raw.verifiedAt === "number" && ["pass", "fail", "indeterminate", "error"].includes(String(raw.decision)) && isCurrentRef(raw.attestation); diff --git a/reference-implementations/dacs-directory/src/catalog/reputation.ts b/reference-implementations/dacs-directory/src/catalog/reputation.ts index 60196e0..56c6ecd 100644 --- a/reference-implementations/dacs-directory/src/catalog/reputation.ts +++ b/reference-implementations/dacs-directory/src/catalog/reputation.ts @@ -32,6 +32,11 @@ export function isNeutralCancellation( ? listingTerms as Record : undefined; const phases = Array.isArray(phaseSummary) ? phaseSummary.filter((phase): phase is Record => Boolean(phase && typeof phase === "object" && !Array.isArray(phase))) : []; + // PAYEE-BOUND COUPLING (issue #17 F2): site (b) of 3. Only "commit-agreement" is recognized + // as commit here. When payee-bound support lands, a session that committed via + // "commit-payee-bound-agreement" (#236) must also count as commitReached — otherwise a + // post-commit abort mis-scores as ST-10 reputation-neutral. Move this together with shapeOk's + // discriminator and the SEPARATORS domain in evidenceGraph.ts / bundlePolicy.ts. const commitReached = phases.some((phase) => phase.kind === "commit-agreement" && phase.outcome === "ok"); return (sellerOutcome === "aborted-by-self" || sellerOutcome === "aborted-by-other") && marker?.claimedPolicy === "pre-commit" && terms?.cancellationPolicy === "pre-commit" && !commitReached; diff --git a/reference-implementations/dacs-directory/src/components/labels.ts b/reference-implementations/dacs-directory/src/components/labels.ts index 29ac59a..c365cd3 100644 --- a/reference-implementations/dacs-directory/src/components/labels.ts +++ b/reference-implementations/dacs-directory/src/components/labels.ts @@ -2,7 +2,6 @@ export const RAIL_LABELS: Record = { "pay-dem": "DEM", "pay-x402": "USDC · x402", - "pay-evm-erc8183": "ERC-8183", }; export const railLabel = (r: string) => RAIL_LABELS[r] ?? r.replace(/^pay-/, ""); /** "negotiate-fixed-price" → "fixed price"; acronym ids get proper casing. */