Skip to content
Merged
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
4 changes: 3 additions & 1 deletion reference-implementations/dacs-directory/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ a.card:hover { border-color: var(--border-hover); background: var(--bg-card-hove
.badge.rail { background: var(--cyan-soft); color: var(--cyan-strong); border-color: var(--cyan-border); }
.badge.ok { background: var(--green-soft); color: var(--green-strong); border-color: var(--green-border); }
.badge.err { background: var(--red-soft); color: var(--red-strong); border-color: var(--red-border); }
.badge.pending { background: var(--accent-soft); color: var(--accent-strong); border-color: var(--accent-border); }

/* ── Stats ────────────────────────────────────────────────────────────────── */
.stat-row { display: flex; flex-wrap: wrap; gap: 16px; margin: 24px 0; }
Expand Down Expand Up @@ -549,6 +550,7 @@ textarea.form-control { min-height: 112px; resize: vertical; }
@keyframes livePulse { 0% { box-shadow: 0 0 0 0 rgba(105,62,255,.35); } 70% { box-shadow: 0 0 0 7px rgba(105,62,255,0); } 100% { box-shadow: 0 0 0 0 rgba(105,62,255,0); } }
.full-proc-result { background: var(--bg-subtle); }.full-proc-result > .result-title { background: var(--bg-card); border-bottom: 1px solid var(--border); }.proc-report { padding: 22px; display: grid; gap: 18px; }.proc-summary { display: grid; grid-template-columns: repeat(4,1fr); gap: 1px; overflow: hidden; border: 1px solid var(--border); border-radius: 13px; background: var(--border); }.proc-summary > div { min-width: 0; padding: 17px; background: var(--bg-card); }.proc-summary span,.proc-panel-head span,.tx-link small { display: block; color: var(--text-muted); font-size: .61rem; font-weight: 800; letter-spacing: .08em; }.proc-summary strong { display: block; overflow-wrap: anywhere; margin-top: 7px; color: var(--text-primary); font-size: .82rem; }.success-text { color: #10845b !important; }.error-text { color: var(--red-strong) !important; }.muted-text { color: var(--text-muted) !important; }.proc-panel { overflow: hidden; border: 1px solid var(--border); border-radius: 13px; background: var(--bg-card); }.proc-panel-head { display: flex; justify-content: space-between; align-items: center; gap: 16px; padding: 17px 18px; border-bottom: 1px solid var(--border); }.proc-panel-head h3 { margin-top: 4px; color: var(--text-primary); font-size: .9rem; }.payment-panel { border-color: rgba(16,132,91,.28); }.payment-panel.unverified-panel { border-color: var(--red-border); }.tx-link { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 17px 18px; background: rgba(16,132,91,.045); color: inherit; text-decoration: none; border-bottom: 1px solid var(--border); }.tx-link.missing-evidence { background: var(--red-soft); }.tx-link span { min-width: 0; }.tx-link code { display: block; overflow: hidden; margin-top: 6px; color: var(--text-primary); font-size: .72rem; text-overflow: ellipsis; }.tx-link b { flex: none; color: #10845b; font-size: .7rem; }.party-row { display: flex; align-items: center; justify-content: center; gap: 14px; padding: 12px 18px; color: var(--text-muted); font-size: .65rem; }.party-row code { margin-left: 4px; color: var(--text-secondary); }.party-row i { color: var(--accent); font-style: normal; }.candidate-row { display: grid; grid-template-columns: 1.5fr .55fr .65fr 1.3fr; gap: 12px; align-items: center; padding: 13px 18px; border-bottom: 1px solid var(--border); color: var(--text-secondary); font-size: .7rem; }.candidate-row:last-child { border-bottom: 0; }.candidate-row strong,.candidate-row small { display: block; }.candidate-row small { margin-top: 3px; color: var(--text-muted); font: .59rem var(--font-scp, monospace); }.candidate-head { color: var(--text-muted); background: var(--bg-subtle); font-size: .6rem; font-weight: 800; letter-spacing: .06em; }.finding-list article { display: grid; grid-template-columns: 74px 1fr; gap: 14px; padding: 16px 18px; border-bottom: 1px solid var(--border); }.finding-list article:last-child { border-bottom: 0; }.severity { align-self: start; padding: 5px 7px; border-radius: 6px; background: rgba(180,95,0,.1); color: #9a5300; text-align: center; font-size: .6rem; font-weight: 800; text-transform: uppercase; }.severity.high,.severity.critical { color: #b72f3c; background: rgba(183,47,60,.1); }.severity.low,.severity.info { color: #2769a8; background: rgba(39,105,168,.1); }.finding-list strong { color: var(--text-primary); font-size: .76rem; }.finding-list p { margin: 5px 0; color: var(--text-secondary); font-size: .7rem; line-height: 1.5; }.finding-list code { color: var(--text-muted); font-size: .62rem; }.empty-report { padding: 18px; color: var(--text-muted); font-size: .74rem; }.verify-grid { display: grid; grid-template-columns: repeat(4,1fr); }.verify-grid > div { display: flex; gap: 10px; align-items: center; padding: 17px; border-right: 1px solid var(--border); }.verify-grid > div:last-child { border-right: 0; }.verify-grid i { display: grid; place-items: center; flex: 0 0 24px; height: 24px; border-radius: 50%; color: #10845b; background: rgba(16,132,91,.09); font-style: normal; font-size: .7rem; }.verify-grid .failed-check i { color: var(--red-strong); background: var(--red-soft); }.verify-grid .failed-check strong { color: var(--red-strong); }.verify-grid span,.verify-grid strong { display: block; }.verify-grid span { color: var(--text-muted); font-size: .62rem; }.verify-grid strong { margin-top: 3px; color: var(--text-primary); font-size: .7rem; }.receipt-row { display: grid; grid-template-columns: 32px minmax(0,1fr) auto; gap: 10px; align-items: center; padding: 12px 18px; border-bottom: 1px solid var(--border); }.receipt-index { color: var(--text-muted); font: .62rem var(--font-scp, monospace); }.receipt-row strong,.receipt-row small { display: block; }.receipt-row strong { color: var(--text-secondary); font-size: .7rem; }.receipt-row small { overflow: hidden; margin-top: 3px; color: var(--text-muted); font: .6rem var(--font-scp, monospace); text-overflow: ellipsis; }.receipt-row a { color: var(--accent); font: .63rem var(--font-scp, monospace); text-decoration: none; }.anchor-details,.raw-result { border-top: 1px solid var(--border); }.proc-report .raw-result { overflow: hidden; border: 1px solid var(--border); border-radius: 13px; background: var(--bg-card); }.proc-report details pre { max-height: 420px; }.anchor-details summary,.raw-result summary { padding: 13px 18px !important; }
.oracle-delivery { padding: 22px 18px; }.oracle-delivery span,.oracle-delivery strong,.oracle-delivery small { display: block; }.oracle-delivery span { color: var(--text-muted); font: 700 .62rem var(--font-scp, monospace); text-transform: uppercase; }.oracle-delivery strong { margin-top: 8px; color: var(--text-primary); font-size: 2rem; letter-spacing: -.04em; }.oracle-delivery small { margin-top: 8px; overflow-wrap: anywhere; color: var(--text-muted); font-size: .66rem; }
.preview-proc-result { border-color: var(--accent-border); box-shadow: 0 16px 50px rgba(105,62,255,.08); }.proc-preview { background: var(--bg-card); }.proc-preview-head { display: flex; align-items: center; justify-content: space-between; gap: 18px; padding: 20px 22px; border-bottom: 1px solid var(--border); background: var(--accent-soft); }.proc-preview-head > div > span { display: block; color: var(--accent-strong); font: 800 .61rem var(--font-scp, monospace); letter-spacing: .1em; }.proc-preview-head h2 { margin-top: 5px; color: var(--text-primary); font-size: 1.05rem; }.proc-preview-state { display: grid; grid-template-columns: repeat(2,1fr); border-bottom: 1px solid var(--border); }.proc-preview-state > div { padding: 16px 22px; }.proc-preview-state > div + div { border-left: 1px solid var(--border); }.proc-preview-state strong,.proc-preview-state small { display: block; }.proc-preview-state strong { color: var(--text-primary); font-size: .76rem; }.proc-preview-state small { margin-top: 5px; color: var(--text-muted); font-size: .68rem; line-height: 1.5; }.preview-delivery { border-bottom: 1px solid var(--border); }.preview-fields { display: grid; grid-template-columns: repeat(4,1fr); border-bottom: 1px solid var(--border); }.preview-fields > div { min-width: 0; padding: 16px 18px; border-right: 1px solid var(--border); }.preview-fields > div:nth-child(4n) { border-right: 0; }.preview-fields span,.preview-fields strong { display: block; overflow-wrap: anywhere; }.preview-fields span { color: var(--text-muted); font-size: .6rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; }.preview-fields strong { margin-top: 6px; color: var(--text-primary); font-size: .75rem; }.proc-preview-warning { display: flex; align-items: center; gap: 14px; padding: 14px 22px; color: var(--text-secondary); background: rgba(180,95,0,.07); border-bottom: 1px solid var(--border); }.proc-preview-warning strong { flex: none; color: #9a5300; font-size: .7rem; }.proc-preview-warning span { font-size: .68rem; line-height: 1.45; }.preview-anchor-list > div { display: grid; grid-template-columns: 130px minmax(0,1fr); gap: 12px; padding: 10px 18px; border-top: 1px solid var(--border); }.preview-anchor-list span { color: var(--text-muted); font-size: .64rem; text-transform: capitalize; }.preview-anchor-list code { overflow: hidden; color: var(--text-secondary); font-size: .64rem; text-overflow: ellipsis; }

.try-agents { margin-top: 82px; }.try-section-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 40px; margin-bottom: 25px; }.try-section-head span { color: var(--accent-strong); font: 700 .63rem var(--font-scp, monospace); letter-spacing: .12em; }.try-section-head h2 { font-size: 1.8rem; letter-spacing: -.025em; margin-top: 5px; }.try-section-head > p { max-width: 420px; color: var(--text-muted); font-size: .8rem; }
.try-agent-grid { display: grid; grid-template-columns: repeat(3, 1fr); border: 1px solid var(--border); border-radius: 16px; overflow: hidden; }.try-agent-grid button { min-height: 190px; padding: 20px; text-align: left; color: var(--text-primary); background: var(--bg-card); border: 0; border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); cursor: pointer; font-family: inherit; }.try-agent-grid button:nth-child(3n){border-right:0}.try-agent-grid button:nth-last-child(-n+3){border-bottom:0}.try-agent-grid button:hover { background: var(--accent-soft); }.try-agent-grid button > span { color: var(--text-muted); font: .65rem var(--font-scp, monospace); }.try-agent-grid strong { display: block; font-size: .9rem; margin-top: 13px; }.try-agent-grid p { color: var(--text-muted); font-size: .72rem; line-height: 1.5; margin: 7px 0 20px; }.try-agent-grid i { color: var(--accent-strong); font-size: .7rem; font-style: normal; font-weight: 700; }
Expand All @@ -563,7 +565,7 @@ textarea.form-control { min-height: 112px; resize: vertical; }
@media (max-width: 580px) {
main { padding-left: 16px; padding-right: 16px; }.nav-inner { padding-left: 16px; padding-right: 16px; }.try-hero { padding-top: 20px; }.try-hero h1 { font-size: 16vw; }
.chat-body { padding: 20px 15px; }.bubble { max-width: 92%; }.ask-box,.job-box,.agent-picker { padding: 18px 15px; }.picker-grid { grid-template-columns: 1fr; }.job-head,.job-actions,.try-section-head,.rail-picker-head,.rail-disclosure,.rail-governance { align-items: stretch; flex-direction: column; }.rail-options { grid-template-columns: 1fr; }.job-actions .try-primary { width: 100%; }.try-agent-grid { grid-template-columns: 1fr; }.try-agent-grid button { border-right: 0 !important; border-bottom: 1px solid var(--border) !important; }.try-agent-grid button:last-child { border-bottom: 0 !important; }
.live-flow { padding: 18px 15px; }.live-flow-head,.proc-panel-head,.tx-link,.result-title { align-items: stretch; flex-direction: column; }.proc-report { padding: 12px; gap: 12px; }.proc-summary { grid-template-columns: 1fr; }.candidate-head { display: none; }.candidate-row { grid-template-columns: 1fr 1fr; }.candidate-row > span:last-child { grid-column: 1 / -1; }.verify-grid { grid-template-columns: 1fr; }.verify-grid > div { border-right: 0; border-bottom: 1px solid var(--border); }.verify-grid > div:last-child { border-bottom: 0; }.finding-list article { grid-template-columns: 1fr; }.receipt-row { grid-template-columns: 26px minmax(0,1fr); }.receipt-row > a,.receipt-row > .muted-text { grid-column: 2; }.party-row { align-items: flex-start; flex-direction: column; }.party-row i { transform: rotate(90deg); }
.live-flow { padding: 18px 15px; }.live-flow-head,.proc-panel-head,.tx-link,.result-title,.proc-preview-head,.proc-preview-warning { align-items: stretch; flex-direction: column; }.proc-report { padding: 12px; gap: 12px; }.proc-summary,.proc-preview-state,.preview-fields { grid-template-columns: 1fr; }.proc-preview-state > div + div { border-left: 0; border-top: 1px solid var(--border); }.preview-fields > div { border-right: 0; border-bottom: 1px solid var(--border); }.candidate-head { display: none; }.candidate-row { grid-template-columns: 1fr 1fr; }.candidate-row > span:last-child { grid-column: 1 / -1; }.verify-grid { grid-template-columns: 1fr; }.verify-grid > div { border-right: 0; border-bottom: 1px solid var(--border); }.verify-grid > div:last-child { border-bottom: 0; }.finding-list article { grid-template-columns: 1fr; }.receipt-row { grid-template-columns: 26px minmax(0,1fr); }.receipt-row > a,.receipt-row > .muted-text { grid-column: 2; }.party-row { align-items: flex-start; flex-direction: column; }.party-row i { transform: rotate(90deg); }.preview-anchor-list > div { grid-template-columns: 1fr; }
}

/* ── /try agent input forms ────────────────────────────────────────────── */
Expand Down
46 changes: 46 additions & 0 deletions reference-implementations/dacs-directory/e2e/try-dacs-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export const completedJob = {
id: "job-e2e-1",
status: "complete",
phase: "complete",
preview: null,
events: [
{ phase: "discovering", label: "Signed listing verified", at, txRef: "mock-listing-tx" },
{ phase: "selecting", label: "Counterparty vet anchored", at, txRef: "mock-vet-tx" },
Expand All @@ -146,6 +147,51 @@ export const completedJob = {
result: acceptedResult,
};

export const securityPreviewJob = {
id: completedJob.id,
status: "running",
phase: "verifying",
events: [
...completedJob.events.slice(0, 4),
{ phase: "verifying", label: "Seller delivery and payment evidence verified", at, txRef: "mock-delivery-tx" },
],
preview: {
kind: "dacs-procurement-delivery-preview",
status: "report-verified-finalising-dacs5",
jobId: "web-auditor-e2e-1",
delivery: {
verified: true,
report: {
version: 1,
target: "(posted content)",
findings: [{
id: "SEC-1",
severity: "high",
title: "Unsafe dynamic execution",
detail: "A dynamic code path requires review.",
file: "app.js",
line: 4,
}],
},
},
anchors: {
listing: "mock-listing-anchor",
agreement: "mock-agreement-anchor",
commitment: "mock-commitment-anchor",
paymentEvidence: "mock-payment-evidence-anchor",
delivery: "mock-delivery-anchor",
deliveryEvidence: "mock-delivery-evidence-anchor",
},
},
};

export const failedSecurityPreviewJob = {
...securityPreviewJob,
status: "failed",
phase: "verifying",
error: "Buyer DACS-5 bundle anchoring failed after payment evidence was recorded.",
};

const x402PaymentTx = `0x${"ab".repeat(32)}`;

export const x402CompletedJob = {
Expand Down
58 changes: 58 additions & 0 deletions reference-implementations/dacs-directory/e2e/try-dacs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
chooseProcurementExample,
completedJob,
expectAcceptedEvidence,
failedSecurityPreviewJob,
installMockGateway,
securityPreviewJob,
x402CompletedJob,
} from "./try-dacs-fixtures.js";

Expand Down Expand Up @@ -211,4 +213,60 @@ test.describe("/try procurement browser safety", () => {
await expect(quote).toHaveAttribute("aria-invalid", "true");
await expect(page.getByText("Required — enter the currency to convert to.")).toBeVisible();
});

test("8. shows verified delivery while DACS-5 remains nonterminal, then accepts only the completed job", async ({ context, page }) => {
let releaseFinalPoll!: () => void;
const finalPollGate = new Promise<void>((resolve) => { releaseFinalPoll = resolve; });
let previewReturned!: () => void;
const previewSeen = new Promise<void>((resolve) => { previewReturned = resolve; });

await installMockGateway(context, {
onProcurementPost: async (route) => {
await fulfillJson(route, securityPreviewJob);
previewReturned();
},
onProcurementGet: async (route) => {
await finalPollGate;
await fulfillJson(route, completedJob);
},
});
await chooseProcurementExample(page);

await page.getByRole("button", { name: /Run the full deal/ }).click();
await previewSeen;

await expect(page.getByRole("heading", { name: "Verified security report is ready" })).toBeVisible();
await expect(page.getByText("DACS-5 finalising", { exact: true })).toBeVisible();
await expect(page.getByText("Not settled-and-accepted yet", { exact: true })).toBeVisible();
await expect(page.getByText("Unsafe dynamic execution", { exact: true })).toBeVisible();
await expect(page.getByText(/DELIVERY VERIFIED · DACS-5 FINALISING/)).toBeVisible();
await expect(page.locator(".journey-step.active").filter({ hasText: "Verify" })).toContainText("two DACS-5 copies are finalising");
await expect(page.locator(".journey-step.active")).toHaveCount(1);
await expect(page.getByRole("heading", { name: "Security Auditor result" })).toHaveCount(0);
await expect(page.getByText("Settled & accepted", { exact: true })).toHaveCount(0);

releaseFinalPoll();
await expectAcceptedEvidence(page);
await expect(page.getByRole("heading", { name: "Verified security report is ready" })).toHaveCount(0);
});

test("9. preserves a verified delivery when DACS-5 finalisation fails without claiming acceptance", async ({ context, page }) => {
await installMockGateway(context, {
onProcurementPost: (route) => fulfillJson(route, securityPreviewJob),
onProcurementGet: (route) => fulfillJson(route, failedSecurityPreviewJob),
});
await chooseProcurementExample(page);

await page.getByRole("button", { name: /Run the full deal/ }).click();

await expect(page.getByRole("heading", { name: "Verified security report is ready" })).toBeVisible();
await expect(page.getByText("DACS-5 needs recovery", { exact: true })).toBeVisible();
await expect(page.getByText("Finalisation stopped safely", { exact: true })).toBeVisible();
await expect(page.getByText(failedSecurityPreviewJob.error, { exact: true }).first()).toBeVisible();
await expect(page.getByText("Unsafe dynamic execution", { exact: true })).toBeVisible();
await expect(page.getByText("Not settled-and-accepted yet", { exact: true })).toBeVisible();
await expect(page.getByText("Settled & accepted", { exact: true })).toHaveCount(0);
await expect(page.getByRole("heading", { name: "Security Auditor result" })).toHaveCount(0);
await expect(page.getByText(/Retrying reuses the same idempotency key/)).toHaveCount(0);
});
});
Loading