@supercuts-walrus/backend 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/buyer-setup.js +23 -9
- package/dist/cli.js +7 -2
- package/dist/dashboard.js +1 -1
- package/dist/deal-engine.d.ts +13 -0
- package/dist/deal-engine.js +59 -34
- package/dist/deal.d.ts +35 -0
- package/dist/deal.js +7 -1
- package/dist/embedding-adapter.d.ts +26 -0
- package/dist/embedding-adapter.js +116 -0
- package/dist/facilitator.js +2 -1
- package/dist/funding.js +10 -1
- package/dist/live-deal-ports.d.ts +2 -1
- package/dist/live-deal-ports.js +23 -4
- package/dist/live-runtime.js +26 -8
- package/dist/main-module.d.ts +5 -0
- package/dist/main-module.js +16 -0
- package/dist/mcp.js +2 -0
- package/dist/memory-scorecard.d.ts +32 -0
- package/dist/memory-scorecard.js +76 -0
- package/dist/memwal.d.ts +28 -0
- package/dist/memwal.js +81 -2
- package/dist/onboarding-doctor.js +7 -5
- package/dist/phase3.d.ts +43 -0
- package/dist/phase3.js +143 -30
- package/dist/seam.js +2 -1
- package/dist/seller-service.js +2 -1
- package/dist/seller-setup.js +9 -7
- package/dist/settlement.d.ts +32 -3
- package/dist/settlement.js +56 -11
- package/dist/testnet-defaults.d.ts +21 -0
- package/dist/testnet-defaults.js +26 -0
- package/package.json +3 -2
package/dist/phase3.js
CHANGED
|
@@ -8,9 +8,10 @@ import { DealJournal } from "./deal.js";
|
|
|
8
8
|
import { LiveRuntime } from "./live-runtime.js";
|
|
9
9
|
import { V2ExitQueue } from "./v2-exits.js";
|
|
10
10
|
import { validateCapabilityManifest } from "./capability.js";
|
|
11
|
-
import { ManualMemory } from "./memwal.js";
|
|
11
|
+
import { ManualMemory, openMemoryText, withMemoryDefaults } from "./memwal.js";
|
|
12
12
|
import { WorkflowMemory } from "./workflow-memory.js";
|
|
13
|
-
import {
|
|
13
|
+
import { buildScorecard } from "./memory-scorecard.js";
|
|
14
|
+
import { canonicalJson, createSettlementPlan, explainMemoryInfluence, isRelevantMemory, settlementMandateHash, settlementPlanHash, validateSettlementMemory, validateSettlementPlan } from "./settlement.js";
|
|
14
15
|
const PACKAGE = "0xf887433ddfd1068f6df3020cd757939b99ceb418f0d8b9924092b01c7aee846e";
|
|
15
16
|
const POOL = "0x873e37b4745316f21a2780893f5fc36f87da559b08923fb4dad6237cd2e95d98";
|
|
16
17
|
const V2_PACKAGE = "0x32d104c1ab422d497df4ae3a6861258694949c86428f886ca338e07bd055cf76";
|
|
@@ -56,6 +57,7 @@ function matchesPlannedEndpoint(planned, actual, requestId) {
|
|
|
56
57
|
return false;
|
|
57
58
|
}
|
|
58
59
|
}
|
|
60
|
+
const memoryQuery = (manifest) => `${manifest.capabilityId} ${manifest.capability} ${manifest.sellerId}`;
|
|
59
61
|
function record(store, scope, id, value) {
|
|
60
62
|
store.db.prepare("INSERT INTO app_records(scope,id,value,updated_at) VALUES(?,?,?,?) ON CONFLICT(scope,id) DO UPDATE SET value=excluded.value,updated_at=excluded.updated_at")
|
|
61
63
|
.run(scope, id, json(value), Date.now());
|
|
@@ -118,11 +120,12 @@ export function initialize(home, args) {
|
|
|
118
120
|
if (liveCredentials?.memory) {
|
|
119
121
|
let memoryConfig;
|
|
120
122
|
try {
|
|
121
|
-
memoryConfig = JSON.parse(liveCredentials.memory.toString());
|
|
123
|
+
memoryConfig = withMemoryDefaults(JSON.parse(liveCredentials.memory.toString()), role);
|
|
122
124
|
}
|
|
123
125
|
catch (error) {
|
|
124
|
-
return fail("LIVE_MEMORY_CONFIG_INVALID",
|
|
126
|
+
return fail("LIVE_MEMORY_CONFIG_INVALID", `memory config is invalid: ${error instanceof TypeError ? error.message : "not valid JSON"}`, error);
|
|
125
127
|
}
|
|
128
|
+
liveCredentials.memory = Buffer.from(JSON.stringify(memoryConfig));
|
|
126
129
|
let embedding;
|
|
127
130
|
try {
|
|
128
131
|
embedding = new URL(memoryConfig.embeddingApiBase);
|
|
@@ -403,9 +406,14 @@ export class Phase3App {
|
|
|
403
406
|
return { blobId: String(item.blobId), distance: 0, legacy: true };
|
|
404
407
|
});
|
|
405
408
|
}
|
|
406
|
-
|
|
409
|
+
/** Trusted provenance needs local authority or this owner's memory-key signature (portable across machines). */
|
|
410
|
+
authenticatedMemory(blobId, memory, signedByOwner = false) {
|
|
407
411
|
if (memory.provenance !== "owner_policy" && memory.provenance !== "verified_outcome")
|
|
408
412
|
return memory;
|
|
413
|
+
if (signedByOwner && (memory.provenance !== "verified_outcome" || memory.evidenceHash)) {
|
|
414
|
+
this.registerMemoryAuthority(blobId, memory);
|
|
415
|
+
return memory;
|
|
416
|
+
}
|
|
409
417
|
const authority = this.store.db.prepare("SELECT fact_hash,provenance,evidence_hash FROM memory_authority WHERE blob_id=?").get(blobId);
|
|
410
418
|
if (authority?.fact_hash === hash(canonicalJson(memory)) && authority.provenance === memory.provenance && (memory.provenance !== "verified_outcome" || authority.evidence_hash === memory.evidenceHash))
|
|
411
419
|
return memory;
|
|
@@ -456,31 +464,126 @@ export class Phase3App {
|
|
|
456
464
|
}
|
|
457
465
|
return existing;
|
|
458
466
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
467
|
+
const manifests = [primary, ...alternatives], recalled = await this.recallSettlementMemories(manifests.map(memoryQuery));
|
|
468
|
+
const input = { operationId, mandate, manifests, ...(typeof args["requested-amount"] === "string" ? { requestedAmount: args["requested-amount"] } : {}), ...(args["privacy-hint"] === "shielded" ? { privacyHint: "shielded" } : {}), ...(args["requested-rail"] === "x402" || args["requested-rail"] === "shielded" ? { requestedRail: args["requested-rail"] } : {}), recalled, maxMemoryDistance: this.profile.policy.maxMemoryDistance ?? 0.35 };
|
|
469
|
+
const plan = this.persistPlan(createSettlementPlan(input));
|
|
470
|
+
if (!get(this.store, "memory-influence", plan.planHash))
|
|
471
|
+
record(this.store, "memory-influence", plan.planHash, explainMemoryInfluence(input, plan));
|
|
472
|
+
return plan;
|
|
473
|
+
}
|
|
474
|
+
/** Recall settlement memories for several scoped queries; plaintext stays in this process. */
|
|
475
|
+
async recallSettlementMemories(queries, limit = 20) {
|
|
476
|
+
const local = this.localVerifiedOutcomes(), merge = (recalled) => [...local, ...recalled.filter((item) => !local.some((other) => other.blobId === item.blobId))]; // the local authoritative copy wins
|
|
477
|
+
const encoded = this.profile.mock ? undefined : this.store.getSecret("live:memory-config");
|
|
478
|
+
if (!encoded)
|
|
479
|
+
return merge(this.recalledMockMemories());
|
|
480
|
+
let config;
|
|
481
|
+
try {
|
|
482
|
+
config = JSON.parse(encoded.toString());
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
return fail("LIVE_MEMORY_CONFIG_INVALID", "encrypted memory config is invalid", error);
|
|
486
|
+
}
|
|
487
|
+
const manual = new ManualMemory(this.store, config), byBlob = new Map();
|
|
488
|
+
try {
|
|
489
|
+
for (const query of [...new Set(queries)])
|
|
490
|
+
for (const item of await manual.recall(query, limit)) {
|
|
491
|
+
const prior = byBlob.get(item.blobId);
|
|
492
|
+
if (prior && prior.distance <= item.distance)
|
|
493
|
+
continue;
|
|
494
|
+
let memory;
|
|
495
|
+
try {
|
|
496
|
+
const opened = openMemoryText(item.text);
|
|
497
|
+
validateSettlementMemory(opened.memory);
|
|
498
|
+
memory = this.authenticatedMemory(item.blobId, opened.memory, Boolean(opened.signedBy && opened.signedBy === manual.signerAddress));
|
|
499
|
+
}
|
|
500
|
+
catch { /* malformed or legacy memory remains advisory */ }
|
|
501
|
+
byBlob.set(item.blobId, { blobId: item.blobId, distance: item.distance, ...(memory ? { memory } : { legacy: true }) });
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
finally {
|
|
505
|
+
manual.destroy();
|
|
506
|
+
}
|
|
507
|
+
return merge([...byBlob.values()]);
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Verified outcomes this runtime wrote itself, including ones still queued for Walrus. They are authoritative
|
|
511
|
+
* for spend and reliability on this machine; semantic recall alone can miss or lag them.
|
|
512
|
+
*/
|
|
513
|
+
localVerifiedOutcomes() {
|
|
514
|
+
const rows = this.store.db.prepare("SELECT operation_id,blob_id FROM memory_jobs WHERE operation_id LIKE 'verified:deal:%' OR operation_id LIKE 'verified:deal-failure:%' ORDER BY created_at").all();
|
|
515
|
+
return rows.flatMap((row) => {
|
|
516
|
+
const payload = this.store.getSecret(`memory-outbox:${row.operation_id}`);
|
|
517
|
+
if (!payload)
|
|
518
|
+
return [];
|
|
463
519
|
try {
|
|
464
|
-
|
|
520
|
+
const memory = JSON.parse(payload.toString());
|
|
521
|
+
validateSettlementMemory(memory, "verified_outcome");
|
|
522
|
+
return [{ blobId: row.blob_id ?? `local:${row.operation_id}`, distance: 0, memory }];
|
|
465
523
|
}
|
|
466
|
-
catch
|
|
467
|
-
return
|
|
524
|
+
catch {
|
|
525
|
+
return [];
|
|
468
526
|
}
|
|
469
|
-
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Orders candidate sellers by what Walrus Memory has verified about them, instead of by price alone.
|
|
531
|
+
* Sellers outside the owner mandate are dropped first so memory can never widen authority.
|
|
532
|
+
*/
|
|
533
|
+
async rankDealCandidates(operationId, candidates, mandate, request = {}) {
|
|
534
|
+
const permitted = candidates.filter((item) => mandate.allowedCounterparties.includes(item.sellerId) && (!mandate.allowedCapabilityIds || mandate.allowedCapabilityIds.includes(item.capabilityId)) && (!mandate.allowedCapabilityClasses || mandate.allowedCapabilityClasses.includes(item.capability)));
|
|
535
|
+
const allowed = permitted.length ? permitted : candidates;
|
|
536
|
+
const recalled = await this.recallSettlementMemories(allowed.map(memoryQuery)), now = Date.now(), maxDistance = this.profile.policy.maxMemoryDistance ?? 0.35;
|
|
537
|
+
const relevant = recalled.filter((item) => isRelevantMemory(item, maxDistance));
|
|
538
|
+
// Only rank sellers that the deal could actually plan and pay: budget, rail, privacy, and price bounds.
|
|
539
|
+
const payable = allowed.filter((manifest) => {
|
|
470
540
|
try {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
catch { /* malformed or legacy memory remains advisory */ } return { blobId: item.blobId, distance: item.distance, ...(memory ? { memory } : { legacy: true }) }; });
|
|
541
|
+
const plan = createSettlementPlan({ operationId: `rank:${operationId}`, mandate, manifests: [manifest], requestedAmount: manifest.priceBand.minimum, recalled, maxMemoryDistance: maxDistance, now, ...(request.requestedRail ? { requestedRail: request.requestedRail } : {}), ...(request.privacyHint ? { privacyHint: request.privacyHint } : {}) });
|
|
542
|
+
if (request.budgetMaximum !== undefined && BigInt(plan.maximumAmount) > BigInt(request.budgetMaximum))
|
|
543
|
+
return false;
|
|
544
|
+
return request.requestedRail ? plan.rail === request.requestedRail : mandate.version !== "supercuts.mandate.v2" || !manifest.supportedRails.includes("shielded") || plan.rail === "shielded";
|
|
477
545
|
}
|
|
478
|
-
|
|
479
|
-
|
|
546
|
+
catch {
|
|
547
|
+
return false;
|
|
480
548
|
}
|
|
481
|
-
}
|
|
482
|
-
const
|
|
483
|
-
|
|
549
|
+
});
|
|
550
|
+
const pool = payable.length ? payable : allowed;
|
|
551
|
+
const scored = pool.map((manifest) => ({ manifest, card: buildScorecard(relevant, { counterparty: manifest.sellerId, capabilityClass: manifest.capability }, now) }));
|
|
552
|
+
const review = (card) => Number(card.lastOutcome === "unresolved" || card.observations > 0 && card.reliability < 0.5);
|
|
553
|
+
scored.sort((a, b) => review(a.card) - review(b.card) || b.card.reliability - a.card.reliability || (BigInt(a.manifest.priceBand.minimum) < BigInt(b.manifest.priceBand.minimum) ? -1 : BigInt(a.manifest.priceBand.minimum) > BigInt(b.manifest.priceBand.minimum) ? 1 : 0) || a.manifest.capabilityId.localeCompare(b.manifest.capabilityId));
|
|
554
|
+
const result = { ordered: scored.map((item) => item.manifest), scorecards: scored.map((item) => ({ ...item.card, capabilityId: item.manifest.capabilityId, priceMinimum: item.manifest.priceBand.minimum })) };
|
|
555
|
+
record(this.store, "deal-ranking", operationId, { candidates: pool, scorecards: result.scorecards, chosen: result.ordered[0]?.capabilityId, createdAt: now });
|
|
556
|
+
return result;
|
|
557
|
+
}
|
|
558
|
+
/** Stores the counterfactual (same mandate, no memory, every candidate) next to the binding deal plan. */
|
|
559
|
+
recordDealMemoryInfluence(dealOperationId, plan, mandate, request = {}) {
|
|
560
|
+
const ranking = get(this.store, "deal-ranking", dealOperationId);
|
|
561
|
+
if (!ranking)
|
|
562
|
+
return undefined;
|
|
563
|
+
const existing = get(this.store, "memory-influence", `deal:${dealOperationId}`);
|
|
564
|
+
if (existing)
|
|
565
|
+
return existing;
|
|
566
|
+
const influence = explainMemoryInfluence({ operationId: plan.operationId, mandate, manifests: ranking.candidates, requestedAmount: plan.openingAmount, ...request }, plan);
|
|
567
|
+
const cited = [...new Set([...influence.citedBlobIds, ...ranking.scorecards.flatMap((item) => item.citedBlobIds)])].sort();
|
|
568
|
+
const value = { ...influence, citedBlobIds: cited };
|
|
569
|
+
record(this.store, "memory-influence", `deal:${dealOperationId}`, value);
|
|
570
|
+
return value;
|
|
571
|
+
}
|
|
572
|
+
memoryInfluence(id) { return get(this.store, "memory-influence", id); }
|
|
573
|
+
/**
|
|
574
|
+
* Owner view of what Supercuts has learned: fresh scorecards recalled from Walrus Memory for every seller
|
|
575
|
+
* it has ranked, plus how often memory changed a payment decision. Counts and blob IDs only, never plaintext.
|
|
576
|
+
*/
|
|
577
|
+
async memoryInsights() {
|
|
578
|
+
const rankings = all(this.store, "deal-ranking");
|
|
579
|
+
const manifests = [...new Map(rankings.flatMap((item) => item.candidates).map((item) => [item.capabilityId, item])).values()];
|
|
580
|
+
const recalled = manifests.length ? await this.recallSettlementMemories(manifests.map(memoryQuery)) : [], now = Date.now(), maxDistance = this.profile.policy.maxMemoryDistance ?? 0.35;
|
|
581
|
+
const relevant = recalled.filter((item) => isRelevantMemory(item, maxDistance));
|
|
582
|
+
const sellers = manifests.map((manifest) => { const card = buildScorecard(relevant, { counterparty: manifest.sellerId, capabilityClass: manifest.capability }, now); return { capabilityId: manifest.capabilityId, capabilityClass: manifest.capability, priceMinimum: manifest.priceBand.minimum, ...card }; }).sort((a, b) => b.reliability - a.reliability || a.capabilityId.localeCompare(b.capabilityId));
|
|
583
|
+
const influences = this.store.db.prepare("SELECT value FROM app_records WHERE scope='memory-influence' AND id LIKE 'deal:%'").all().map((row) => JSON.parse(row.value));
|
|
584
|
+
const jobs = this.store.db.prepare("SELECT status, COUNT(*) AS count FROM memory_jobs GROUP BY status").all();
|
|
585
|
+
const indexed = this.store.db.prepare("SELECT COUNT(*) AS count FROM memory_inventory WHERE deleted=0").get().count;
|
|
586
|
+
return { version: "supercuts.memory-insights.v1", memories: { byStatus: Object.fromEntries(jobs.map((row) => [row.status, row.count])), indexed, recalled: recalled.length }, decisions: { deals: influences.length, changedSeller: influences.filter((item) => item.changedSeller).length, changedRail: influences.filter((item) => item.changedRail).length, changedRisk: influences.filter((item) => item.changedRisk).length, changedMaximum: influences.filter((item) => item.changedMaximum).length }, sellers };
|
|
484
587
|
}
|
|
485
588
|
typedMemory(args, provenance) {
|
|
486
589
|
const encoded = required(args, "memory");
|
|
@@ -545,7 +648,7 @@ export class Phase3App {
|
|
|
545
648
|
manual.destroy();
|
|
546
649
|
}
|
|
547
650
|
}
|
|
548
|
-
enqueueVerifiedOutcome(event, operationId, evidence, planHash, fallback) {
|
|
651
|
+
enqueueVerifiedOutcome(event, operationId, evidence, planHash, fallback, telemetry = {}) {
|
|
549
652
|
const plan = planHash ? this.storedPlan(planHash) : undefined, jobId = `verified:${event}:${operationId}`, evidenceHash = hash(canonicalJson(evidence));
|
|
550
653
|
const prior = this.store.getSecret(`memory-outbox:${jobId}`);
|
|
551
654
|
if (prior) {
|
|
@@ -555,7 +658,7 @@ export class Phase3App {
|
|
|
555
658
|
fail("MEMORY_OPERATION_CONFLICT", "verified outcome ID identifies different evidence or scope");
|
|
556
659
|
return;
|
|
557
660
|
}
|
|
558
|
-
const memory = { version: "supercuts.settlement-memory.v1", kind: "outcome", provenance: "verified_outcome", scope: { ...(plan ? { capabilityId: plan.capabilityId, capabilityClass: plan.capabilityClass, counterparty: plan.sellerId, network: plan.network, asset: plan.asset } : { ...(fallback?.counterparty ? { counterparty: fallback.counterparty } : {}), ...(fallback?.asset ? { asset: fallback.asset } : {}), network: "sui:testnet" }) }, createdAt: Date.now(), evidenceHash, signal: { outcome: String(evidence.status ?? "") === "unresolved" ? "unresolved" : String(evidence.status ?? "") === "failed" ? "failed" : "succeeded" } };
|
|
661
|
+
const memory = { version: "supercuts.settlement-memory.v1", kind: "outcome", provenance: "verified_outcome", scope: { ...(plan ? { capabilityId: plan.capabilityId, capabilityClass: plan.capabilityClass, counterparty: plan.sellerId, network: plan.network, asset: plan.asset } : { ...(fallback?.counterparty ? { counterparty: fallback.counterparty } : {}), ...(fallback?.asset ? { asset: fallback.asset } : {}), network: "sui:testnet" }) }, createdAt: Date.now(), evidenceHash, signal: { outcome: String(evidence.status ?? "") === "unresolved" ? "unresolved" : String(evidence.status ?? "") === "failed" ? "failed" : "succeeded", ...telemetry } };
|
|
559
662
|
validateSettlementMemory(memory, "verified_outcome");
|
|
560
663
|
const factHash = hash(canonicalJson(memory));
|
|
561
664
|
if (this.profile.mock) {
|
|
@@ -567,11 +670,21 @@ export class Phase3App {
|
|
|
567
670
|
this.store.putSecretOnce(`memory-outbox:${jobId}`, Buffer.from(canonicalJson(memory)));
|
|
568
671
|
this.store.db.prepare("INSERT OR IGNORE INTO memory_jobs(operation_id,kind,fact_hash,blob_id,status,created_at) VALUES(?,?,?,NULL,'pending',?)").run(jobId, memory.kind, factHash, Date.now());
|
|
569
672
|
}
|
|
673
|
+
/** Queues a verified seller fault as a Walrus outcome memory so later discovery demotes that seller. */
|
|
674
|
+
recordVerifiedDealFailure(operationId, planHash, fault) {
|
|
675
|
+
if (this.profile.mock || this.profile.role !== "buyer")
|
|
676
|
+
fail("DEAL_LIVE_REQUIRED", "verified deal failures require a live buyer role");
|
|
677
|
+
if (this.store.getSecret(`memory-outbox:verified:deal-failure:${operationId}`))
|
|
678
|
+
return;
|
|
679
|
+
const plan = this.storedPlan(planHash);
|
|
680
|
+
this.enqueueVerifiedOutcome("deal-failure", operationId, { status: "failed", stage: fault.stage, code: fault.code, ...(fault.paymentDigest ? { paymentDigest: fault.paymentDigest } : {}) }, planHash, undefined, { rail: plan.rail, ...(fault.paymentDigest ? { amount: plan.openingAmount } : {}) });
|
|
681
|
+
}
|
|
570
682
|
recordVerifiedDealOutcome(operationId, planHash, paymentDigest, artifactSha256) {
|
|
571
683
|
if (this.profile.mock || this.profile.role !== "buyer")
|
|
572
684
|
fail("DEAL_LIVE_REQUIRED", "verified deal outcome requires a live buyer role");
|
|
573
685
|
const jobId = `verified:deal:${operationId}`;
|
|
574
|
-
this.
|
|
686
|
+
const plan = this.storedPlan(planHash), timings = this.store.db.prepare("SELECT COALESCE(SUM(duration_ms),0) AS total FROM deal_timings WHERE operation_id=? AND stage IN ('negotiation','payment','delivery')").get(operationId);
|
|
687
|
+
this.enqueueVerifiedOutcome("deal", operationId, { status: "succeeded", paymentDigest, artifactSha256 }, planHash, undefined, { amount: plan.openingAmount, rail: plan.rail, ...(Number.isSafeInteger(timings.total) && timings.total > 0 ? { latencyMs: timings.total } : {}) });
|
|
575
688
|
const payload = this.store.getSecret(`memory-outbox:${jobId}`) ?? fail("DEAL_MEMORY_INVALID", "verified outcome payload is missing");
|
|
576
689
|
return { payloadHash: hash(payload), queued: true };
|
|
577
690
|
}
|
|
@@ -645,7 +758,7 @@ export class Phase3App {
|
|
|
645
758
|
const query = [memory.scope.capabilityId, memory.scope.counterparty, memory.signal.outcome].filter(Boolean).join(" ");
|
|
646
759
|
const recalled = await manual.recall(query, 100);
|
|
647
760
|
blobId = recalled.find((item) => { try {
|
|
648
|
-
return canonicalJson(
|
|
761
|
+
return canonicalJson(openMemoryText(item.text).memory) === canonicalJson(memory);
|
|
649
762
|
}
|
|
650
763
|
catch {
|
|
651
764
|
return false;
|
|
@@ -945,9 +1058,9 @@ export class Phase3App {
|
|
|
945
1058
|
if (command === "memory.recall") {
|
|
946
1059
|
const recalled = await manual.recall(required(args, "query"));
|
|
947
1060
|
const citations = recalled.filter((item) => item.distance <= (this.profile.policy.maxMemoryDistance ?? 0.35)).map((item) => { try {
|
|
948
|
-
const
|
|
1061
|
+
const opened = openMemoryText(item.text), memory = opened.memory;
|
|
949
1062
|
validateSettlementMemory(memory);
|
|
950
|
-
const trusted = this.authenticatedMemory(item.blobId, memory);
|
|
1063
|
+
const trusted = this.authenticatedMemory(item.blobId, memory, Boolean(opened.signedBy && opened.signedBy === manual.signerAddress));
|
|
951
1064
|
return { blobId: item.blobId, kind: trusted.kind, provenance: trusted.provenance, distance: item.distance };
|
|
952
1065
|
}
|
|
953
1066
|
catch {
|
package/dist/seam.js
CHANGED
|
@@ -112,6 +112,7 @@ process.stdin.on("end", async () => {
|
|
|
112
112
|
catch (error) {
|
|
113
113
|
const shaped = errorOutput(error);
|
|
114
114
|
const code = String(shaped.error.code ?? "INTERNAL_ERROR");
|
|
115
|
-
|
|
115
|
+
const retryable = ["DELIVERY_IN_PROGRESS", "MEMORY_JOB_PENDING", "TATUM_TIMEOUT", "X402_FACILITATOR_UNAVAILABLE"].includes(code);
|
|
116
|
+
return write(envelope(request.requestId, { ok: false, error: { ...shaped.error, retryable, ...(retryable ? { recovery: "transient: retry or resume later with the same operation ID; never start a second payment", retryWithSameOperationId: true } : {}) } }), 1);
|
|
116
117
|
}
|
|
117
118
|
});
|
package/dist/seller-service.js
CHANGED
|
@@ -17,6 +17,7 @@ import { captureSellerHome, decryptSellerSnapshot, encryptSellerSnapshot, restor
|
|
|
17
17
|
import { X402ResourceService } from "./x402-resource-service.js";
|
|
18
18
|
import { LiveSellerTransport } from "./live-seller-transport.js";
|
|
19
19
|
import { registerDirectoryCapability } from "./seller-directory.js";
|
|
20
|
+
import { isMainModule } from "./main-module.js";
|
|
20
21
|
const isV2 = (row) => row.preview.genericV2 === true;
|
|
21
22
|
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
22
23
|
const validId = (value, name) => typeof value === "string" && /^[a-zA-Z0-9._:-]{1,128}$/.test(value) ? value : fail("SELLER_REQUEST_INVALID", `${name} is invalid`);
|
|
@@ -399,7 +400,7 @@ export class SellerService {
|
|
|
399
400
|
close() { for (const slot of this.#slots.values())
|
|
400
401
|
slot.app.close(); rmSync(this.#root, { recursive: true, force: true }); }
|
|
401
402
|
}
|
|
402
|
-
if (
|
|
403
|
+
if (isMainModule(import.meta.url)) {
|
|
403
404
|
const required = (key) => process.env[key] ?? fail("SELLER_CONFIG_MISSING", `${key} is required`);
|
|
404
405
|
const port = Number(process.env.PORT ?? "3000");
|
|
405
406
|
const origin = required("SELLER_ORIGIN");
|
package/dist/seller-setup.js
CHANGED
|
@@ -5,13 +5,14 @@ import { createInterface } from "node:readline/promises";
|
|
|
5
5
|
import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";
|
|
6
6
|
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
|
|
7
7
|
import { fail } from "./errors.js";
|
|
8
|
+
import { TESTNET_DEFAULTS, withTestnetDefaults } from "./testnet-defaults.js";
|
|
8
9
|
import { initialize } from "./phase3.js";
|
|
9
10
|
function ownerFile(path) {
|
|
10
11
|
if ((statSync(path).mode & 0o077) !== 0)
|
|
11
12
|
fail("SETUP_PERMISSIONS", `${path} must be owner-only`);
|
|
12
13
|
}
|
|
13
14
|
function validateConfig(config) {
|
|
14
|
-
|
|
15
|
+
withTestnetDefaults(config);
|
|
15
16
|
for (const key of ["deploymentConfig", "tatumKeyFile", "writeGrpc", "tatumGrpc", "walrusPublisher", "walrusAggregator"]) {
|
|
16
17
|
if (typeof config[key] !== "string" || !config[key])
|
|
17
18
|
fail("SETUP_INVALID", `${key} is required`);
|
|
@@ -81,14 +82,15 @@ export async function promptSellerSetup(home) {
|
|
|
81
82
|
fail("SETUP_INPUT_REQUIRED", "interactive seller setup requires a terminal; use --config with an owner-only JSON file");
|
|
82
83
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
83
84
|
try {
|
|
84
|
-
const ask = async (label) => (await rl.question(`${label}: `)).trim();
|
|
85
|
+
const ask = async (label, fallback) => (await rl.question(fallback ? `${label} [${fallback}]: ` : `${label}: `)).trim() || fallback || "";
|
|
86
|
+
process.stderr.write("Press Enter to accept a bracketed default. Credentials are read from owner-only files, never typed here.\n");
|
|
85
87
|
const config = {
|
|
86
88
|
deploymentConfig: await ask("Pinned v2 Testnet deployment config path (blank for bundled config)"),
|
|
87
|
-
tatumKeyFile: await ask("Owner-only Tatum key file path"),
|
|
88
|
-
writeGrpc: await ask("Sui write gRPC URL"),
|
|
89
|
-
tatumGrpc: await ask("Tatum read gRPC URL"),
|
|
90
|
-
walrusPublisher: await ask("Walrus publisher URL"),
|
|
91
|
-
walrusAggregator: await ask("Walrus aggregator URL"),
|
|
89
|
+
tatumKeyFile: await ask("Owner-only Tatum API key file path"),
|
|
90
|
+
writeGrpc: await ask("Sui write gRPC URL", TESTNET_DEFAULTS.writeGrpc),
|
|
91
|
+
tatumGrpc: await ask("Tatum read gRPC URL", TESTNET_DEFAULTS.tatumGrpc),
|
|
92
|
+
walrusPublisher: await ask("Walrus publisher URL", TESTNET_DEFAULTS.walrusPublisher),
|
|
93
|
+
walrusAggregator: await ask("Walrus aggregator URL", TESTNET_DEFAULTS.walrusAggregator),
|
|
92
94
|
};
|
|
93
95
|
const snarkjs = await ask("SnarkJS executable path (blank for deployment-relative default)");
|
|
94
96
|
const converter = await ask("Groth16 converter path (blank for deployment-relative default)");
|
package/dist/settlement.d.ts
CHANGED
|
@@ -25,6 +25,10 @@ export interface SettlementMemoryV1 {
|
|
|
25
25
|
reliability?: number;
|
|
26
26
|
quality?: number;
|
|
27
27
|
outcome?: "succeeded" | "failed" | "unresolved";
|
|
28
|
+
amount?: string;
|
|
29
|
+
latencyMs?: number;
|
|
30
|
+
rail?: CapabilityRail;
|
|
31
|
+
periodMs?: number;
|
|
28
32
|
};
|
|
29
33
|
}
|
|
30
34
|
export interface SettlementCitation {
|
|
@@ -33,7 +37,7 @@ export interface SettlementCitation {
|
|
|
33
37
|
provenance: SettlementMemoryProvenance;
|
|
34
38
|
distance: number;
|
|
35
39
|
}
|
|
36
|
-
export type SettlementReasonCode = "MANDATE_DEFAULTS_APPLIED" | "MEMORY_PRIVACY_APPLIED" | "MEMORY_BUDGET_APPLIED" | "MEMORY_OUTCOME_APPLIED" | "MEMORY_LOW_RELEVANCE_IGNORED" | "MEMORY_UNTRUSTED_ADVISORY" | "OWNER_POLICY_CONFLICT" | "RISK_LOW" | "RISK_MEDIUM" | "RISK_REVIEW_REQUIRED" | "RAIL_X402_SELECTED" | "RAIL_SHIELDED_SELECTED" | "REQUESTED_AMOUNT_CLAMPED" | "OWNER_REVIEW_APPROVED";
|
|
40
|
+
export type SettlementReasonCode = "MANDATE_DEFAULTS_APPLIED" | "MEMORY_PRIVACY_APPLIED" | "MEMORY_BUDGET_APPLIED" | "MEMORY_OUTCOME_APPLIED" | "MEMORY_LOW_RELEVANCE_IGNORED" | "MEMORY_UNTRUSTED_ADVISORY" | "OWNER_POLICY_CONFLICT" | "RISK_LOW" | "RISK_MEDIUM" | "RISK_REVIEW_REQUIRED" | "RAIL_X402_SELECTED" | "RAIL_SHIELDED_SELECTED" | "REQUESTED_AMOUNT_CLAMPED" | "OWNER_REVIEW_APPROVED" | "MEMORY_RELIABILITY_LEARNED" | "MEMORY_SELLER_DEMOTED" | "MEMORY_SPEND_LIMIT_REACHED";
|
|
37
41
|
export interface SettlementPlanV1 {
|
|
38
42
|
version: typeof SETTLEMENT_PLAN_VERSION;
|
|
39
43
|
planHash: string;
|
|
@@ -81,7 +85,7 @@ export declare function canonicalJson(value: unknown): string;
|
|
|
81
85
|
export declare const settlementMandateHash: (mandate: PlanningMandate) => string;
|
|
82
86
|
export declare function settlementPlanHash(plan: Omit<SettlementPlanV1, "planHash">): string;
|
|
83
87
|
export declare function validateSettlementMemory(value: unknown, assignedProvenance?: SettlementMemoryProvenance): asserts value is SettlementMemoryV1;
|
|
84
|
-
export
|
|
88
|
+
export interface PlanningInput {
|
|
85
89
|
operationId: string;
|
|
86
90
|
mandate: PlanningMandate;
|
|
87
91
|
manifests: CapabilityManifest[];
|
|
@@ -91,5 +95,30 @@ export declare function createSettlementPlan(input: {
|
|
|
91
95
|
recalled?: RecalledSettlementMemory[];
|
|
92
96
|
maxMemoryDistance?: number;
|
|
93
97
|
now?: number;
|
|
94
|
-
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Trusted typed memories (owner policy or verified outcome) are matched by their structured scope, so a
|
|
101
|
+
* semantic distance cutoff would only drop real evidence: embeddings of JSON records rank far from scope
|
|
102
|
+
* queries. Advisory and legacy text still needs semantic relevance.
|
|
103
|
+
*/
|
|
104
|
+
export declare function isRelevantMemory(item: RecalledSettlementMemory, maxDistance: number): boolean;
|
|
105
|
+
export declare function createSettlementPlan(input: PlanningInput): SettlementPlanV1;
|
|
95
106
|
export declare function validateSettlementPlan(value: unknown): asserts value is SettlementPlanV1;
|
|
107
|
+
export interface MemoryDecision {
|
|
108
|
+
sellerId: string;
|
|
109
|
+
capabilityId: string;
|
|
110
|
+
rail: CapabilityRail;
|
|
111
|
+
riskLevel: SettlementRisk;
|
|
112
|
+
maximumAmount: string;
|
|
113
|
+
}
|
|
114
|
+
export interface MemoryInfluence {
|
|
115
|
+
changedSeller: boolean;
|
|
116
|
+
changedRail: boolean;
|
|
117
|
+
changedRisk: boolean;
|
|
118
|
+
changedMaximum: boolean;
|
|
119
|
+
withoutMemory?: MemoryDecision;
|
|
120
|
+
withMemory: MemoryDecision;
|
|
121
|
+
citedBlobIds: string[];
|
|
122
|
+
}
|
|
123
|
+
/** Counterfactual: what the same mandate would have paid without any recalled memory. Not part of the plan hash. */
|
|
124
|
+
export declare function explainMemoryInfluence(input: PlanningInput, plan: SettlementPlanV1): MemoryInfluence;
|
package/dist/settlement.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { fail } from "./errors.js";
|
|
3
|
+
import { buildScorecard, spentSince } from "./memory-scorecard.js";
|
|
3
4
|
export const SETTLEMENT_PLAN_VERSION = "supercuts.settlement-plan.v1";
|
|
4
5
|
export const SETTLEMENT_MEMORY_VERSION = "supercuts.settlement-memory.v1";
|
|
5
6
|
export function canonicalJson(value) {
|
|
@@ -38,7 +39,7 @@ export function validateSettlementMemory(value, assignedProvenance) {
|
|
|
38
39
|
fail("SETTLEMENT_MEMORY_INVALID", "memory observation must be a short non-empty string");
|
|
39
40
|
const scope = raw.scope, signal = raw.signal;
|
|
40
41
|
exactKeys(scope, ["counterparty", "capabilityId", "capabilityClass", "network", "asset"]);
|
|
41
|
-
exactKeys(signal, ["requiresShielded", "maximumAmount", "reliability", "quality", "outcome"]);
|
|
42
|
+
exactKeys(signal, ["requiresShielded", "maximumAmount", "reliability", "quality", "outcome", "amount", "latencyMs", "rail", "periodMs"]);
|
|
42
43
|
for (const item of Object.values(scope))
|
|
43
44
|
if (typeof item !== "string" || !item || item.length > 1024)
|
|
44
45
|
fail("SETTLEMENT_MEMORY_INVALID", "memory scope fields must be non-empty strings");
|
|
@@ -51,6 +52,13 @@ export function validateSettlementMemory(value, assignedProvenance) {
|
|
|
51
52
|
fail("SETTLEMENT_MEMORY_INVALID", "memory scores must be finite values in [0,1]");
|
|
52
53
|
if (signal.outcome !== undefined && !["succeeded", "failed", "unresolved"].includes(signal.outcome))
|
|
53
54
|
fail("SETTLEMENT_MEMORY_INVALID", "memory outcome is invalid");
|
|
55
|
+
if (signal.amount !== undefined && !unsigned(signal.amount) || signal.rail !== undefined && !["x402", "shielded"].includes(signal.rail))
|
|
56
|
+
fail("SETTLEMENT_MEMORY_INVALID", "memory payment amount or rail is invalid");
|
|
57
|
+
for (const value of [signal.latencyMs, signal.periodMs])
|
|
58
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0))
|
|
59
|
+
fail("SETTLEMENT_MEMORY_INVALID", "memory durations must be non-negative integers");
|
|
60
|
+
if (signal.periodMs !== undefined && (raw.kind !== "budget_limit" || signal.periodMs === 0))
|
|
61
|
+
fail("SETTLEMENT_MEMORY_INVALID", "only budget limits may declare a positive period");
|
|
54
62
|
const required = { privacy_rule: "requiresShielded", budget_limit: "maximumAmount", reliability: "reliability", quality: "quality", outcome: "outcome" };
|
|
55
63
|
if (signal[required[raw.kind]] === undefined)
|
|
56
64
|
fail("SETTLEMENT_MEMORY_INVALID", "memory kind does not have its required signal");
|
|
@@ -64,6 +72,18 @@ function specificity(scope, manifest) {
|
|
|
64
72
|
return -1;
|
|
65
73
|
return scope.capabilityId ? 4 : scope.capabilityClass ? 3 : scope.counterparty ? 2 : 1;
|
|
66
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Trusted typed memories (owner policy or verified outcome) are matched by their structured scope, so a
|
|
77
|
+
* semantic distance cutoff would only drop real evidence: embeddings of JSON records rank far from scope
|
|
78
|
+
* queries. Advisory and legacy text still needs semantic relevance.
|
|
79
|
+
*/
|
|
80
|
+
export function isRelevantMemory(item, maxDistance) {
|
|
81
|
+
if (!Number.isFinite(item.distance) || item.distance < 0)
|
|
82
|
+
return false;
|
|
83
|
+
if (item.memory && (item.memory.provenance === "owner_policy" || item.memory.provenance === "verified_outcome"))
|
|
84
|
+
return true;
|
|
85
|
+
return item.distance <= maxDistance;
|
|
86
|
+
}
|
|
67
87
|
export function createSettlementPlan(input) {
|
|
68
88
|
const now = input.now ?? Date.now(), maxDistance = input.maxMemoryDistance ?? 0.35;
|
|
69
89
|
if (!input.operationId || input.operationId.length > 256 || !input.manifests.length)
|
|
@@ -72,8 +92,9 @@ export function createSettlementPlan(input) {
|
|
|
72
92
|
fail("SETTLEMENT_PLAN_INVALID", "requestedAmount must be canonical unsigned base units");
|
|
73
93
|
if (!Number.isFinite(maxDistance) || maxDistance < 0 || maxDistance > 1 || now >= input.mandate.expiresAt)
|
|
74
94
|
fail("SETTLEMENT_PLAN_DENIED", "planning limits or owner mandate are invalid");
|
|
75
|
-
const
|
|
76
|
-
const
|
|
95
|
+
const relevant = (item) => isRelevantMemory(item, maxDistance);
|
|
96
|
+
const ignored = (input.recalled ?? []).filter((item) => !relevant(item));
|
|
97
|
+
const recalled = (input.recalled ?? []).filter(relevant);
|
|
77
98
|
for (const item of recalled)
|
|
78
99
|
if (item.memory)
|
|
79
100
|
validateSettlementMemory(item.memory);
|
|
@@ -99,19 +120,30 @@ export function createSettlementPlan(input) {
|
|
|
99
120
|
const rails = manifest.supportedRails.filter((rail) => (!input.mandate.allowedRails || input.mandate.allowedRails.includes(rail)) && (!requiresShielded || rail === "shielded") && (input.mandate.version !== "supercuts.mandate.v2" || rail !== "x402" || input.mandate.allowPublicX402 === true) && (!input.requestedRail || rail === input.requestedRail));
|
|
100
121
|
if (!rails.length)
|
|
101
122
|
continue;
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
|
|
123
|
+
const budgetMemories = trusted.filter((item) => item.memory.kind === "budget_limit").map((item) => item.memory);
|
|
124
|
+
const budgets = budgetMemories.filter((item) => item.signal.periodMs === undefined).map((item) => BigInt(item.signal.maximumAmount));
|
|
125
|
+
// Periodic owner budgets subtract the verified spend that Walrus Memory remembers for the same scope.
|
|
126
|
+
const remaining = budgetMemories.filter((item) => item.signal.periodMs !== undefined).map((item) => { const limit = BigInt(item.signal.maximumAmount), spent = spentSince(recalled, item.scope, now - item.signal.periodMs); return limit > spent ? limit - spent : 0n; });
|
|
127
|
+
const minimumPrice = BigInt(manifest.priceBand.minimum);
|
|
128
|
+
let maximum = [BigInt(manifest.priceBand.maximum), BigInt(input.mandate.perDealCeiling), ...budgets, ...remaining].reduce((a, b) => a < b ? a : b);
|
|
129
|
+
const spendLimited = maximum < minimumPrice && remaining.some((value) => value < minimumPrice) && [BigInt(manifest.priceBand.maximum), BigInt(input.mandate.perDealCeiling), ...budgets].every((value) => value >= minimumPrice);
|
|
130
|
+
if (maximum < minimumPrice && !spendLimited)
|
|
105
131
|
continue;
|
|
106
|
-
|
|
107
|
-
|
|
132
|
+
if (spendLimited)
|
|
133
|
+
maximum = minimumPrice;
|
|
134
|
+
const card = buildScorecard(recalled, { counterparty: manifest.sellerId, capabilityClass: manifest.capability }, now);
|
|
135
|
+
const explicit = latest("reliability")[0];
|
|
136
|
+
const reliability = explicit?.provenance === "owner_policy" ? explicit.signal.reliability : card.observations ? card.reliability : explicit?.signal.reliability ?? 0.5;
|
|
137
|
+
const quality = latest("quality")[0]?.signal.quality ?? 0.5, outcome = card.lastOutcome ?? latest("outcome")[0]?.signal.outcome;
|
|
138
|
+
const risk = conflict || spendLimited || outcome === "unresolved" || reliability < 0.5 ? "review_required" : reliability >= 0.8 ? "low" : "medium";
|
|
108
139
|
for (const rail of rails)
|
|
109
|
-
candidates.push({ manifest, rail, reliability, quality, maximum, risk, citations, conflict, reasons: ["MANDATE_DEFAULTS_APPLIED", ...(policies.length ? ["MEMORY_PRIVACY_APPLIED"] : []), ...(
|
|
140
|
+
candidates.push({ manifest, rail, reliability, quality, maximum, risk, citations, conflict, card, reasons: ["MANDATE_DEFAULTS_APPLIED", ...(policies.length ? ["MEMORY_PRIVACY_APPLIED"] : []), ...(budgetMemories.length ? ["MEMORY_BUDGET_APPLIED"] : []), ...(spendLimited ? ["MEMORY_SPEND_LIMIT_REACHED"] : []), ...(outcome ? ["MEMORY_OUTCOME_APPLIED"] : []), ...(card.observations ? ["MEMORY_RELIABILITY_LEARNED"] : []), ...(citations.some((c) => ["agent_observation", "legacy_untrusted"].includes(c.provenance)) ? ["MEMORY_UNTRUSTED_ADVISORY"] : []), ...(ignored.length ? ["MEMORY_LOW_RELEVANCE_IGNORED"] : [])] });
|
|
110
141
|
}
|
|
111
|
-
candidates.sort((a, b) => b.reliability - a.reliability || b.quality - a.quality || (a.maximum < b.maximum ? -1 : a.maximum > b.maximum ? 1 : 0) || a.manifest.capabilityId.localeCompare(b.manifest.capabilityId) || (a.rail === b.rail ? 0 : input.mandate.version === "supercuts.mandate.v2" ? a.rail === "shielded" ? -1 : 1 : a.rail === "x402" ? -1 : 1));
|
|
142
|
+
candidates.sort((a, b) => Number(a.risk === "review_required") - Number(b.risk === "review_required") || b.reliability - a.reliability || b.quality - a.quality || (a.maximum < b.maximum ? -1 : a.maximum > b.maximum ? 1 : 0) || a.manifest.capabilityId.localeCompare(b.manifest.capabilityId) || (a.rail === b.rail ? 0 : input.mandate.version === "supercuts.mandate.v2" ? a.rail === "shielded" ? -1 : 1 : a.rail === "x402" ? -1 : 1));
|
|
112
143
|
const selected = candidates[0] ?? fail("SETTLEMENT_PLAN_DENIED", "no capability remains within the owner mandate, privacy rules, and price bounds");
|
|
113
144
|
const minimum = BigInt(selected.manifest.priceBand.minimum), desired = input.requestedAmount === undefined ? minimum : BigInt(input.requestedAmount), opening = desired < minimum ? minimum : desired > selected.maximum ? selected.maximum : desired;
|
|
114
|
-
const
|
|
145
|
+
const demoted = candidates.some((item) => item.manifest.sellerId !== selected.manifest.sellerId && item.card.observations > 0 && item.reliability < selected.reliability);
|
|
146
|
+
const reasons = [...selected.reasons, ...(demoted ? ["MEMORY_SELLER_DEMOTED"] : []), ...(selected.conflict ? ["OWNER_POLICY_CONFLICT"] : []), ...(opening !== desired ? ["REQUESTED_AMOUNT_CLAMPED"] : []), selected.rail === "shielded" ? "RAIL_SHIELDED_SELECTED" : "RAIL_X402_SELECTED", selected.risk === "low" ? "RISK_LOW" : selected.risk === "medium" ? "RISK_MEDIUM" : "RISK_REVIEW_REQUIRED"];
|
|
115
147
|
const withoutHash = { version: SETTLEMENT_PLAN_VERSION, operationId: input.operationId, status: selected.risk === "review_required" ? "review_required" : "approved", mandateHash: settlementMandateHash(input.mandate), manifestHash: selected.manifest.manifestHash, capabilityId: selected.manifest.capabilityId, capabilityClass: selected.manifest.capability, sellerId: selected.manifest.sellerId, endpoint: selected.manifest.endpoint, network: "sui:testnet", asset: selected.manifest.asset, rail: selected.rail, openingAmount: opening.toString(), maximumAmount: selected.maximum.toString(), manifestMinimum: selected.manifest.priceBand.minimum, manifestMaximum: selected.manifest.priceBand.maximum, riskLevel: selected.risk, createdAt: now, expiresAt: Math.min(now + 300_000, input.mandate.expiresAt, selected.manifest.expiresAt), citations: selected.citations.sort((a, b) => a.blobId.localeCompare(b.blobId)), reasonCodes: [...new Set(reasons)] };
|
|
116
148
|
return { ...withoutHash, planHash: settlementPlanHash(withoutHash) };
|
|
117
149
|
}
|
|
@@ -139,3 +171,16 @@ export function validateSettlementPlan(value) {
|
|
|
139
171
|
if (!Array.isArray(plan.reasonCodes) || !Array.isArray(plan.citations) || plan.citations.some((citation) => !object(citation) || Object.keys(citation).some((key) => !["blobId", "kind", "provenance", "distance"].includes(key)) || typeof citation.blobId !== "string" || !citation.blobId || typeof citation.distance !== "number" || !Number.isFinite(citation.distance) || citation.distance < 0 || citation.distance > 1))
|
|
140
172
|
fail("SETTLEMENT_PLAN_INVALID", "settlement plan citations or reasons are invalid");
|
|
141
173
|
}
|
|
174
|
+
const decision = (plan) => ({ sellerId: plan.sellerId, capabilityId: plan.capabilityId, rail: plan.rail, riskLevel: plan.riskLevel, maximumAmount: plan.maximumAmount });
|
|
175
|
+
/** Counterfactual: what the same mandate would have paid without any recalled memory. Not part of the plan hash. */
|
|
176
|
+
export function explainMemoryInfluence(input, plan) {
|
|
177
|
+
let withoutMemory;
|
|
178
|
+
try {
|
|
179
|
+
withoutMemory = decision(createSettlementPlan({ ...input, recalled: [], now: plan.createdAt }));
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
withoutMemory = undefined;
|
|
183
|
+
}
|
|
184
|
+
const withMemory = decision(plan);
|
|
185
|
+
return { changedSeller: withoutMemory?.sellerId !== withMemory.sellerId, changedRail: withoutMemory?.rail !== withMemory.rail, changedRisk: withoutMemory?.riskLevel !== withMemory.riskLevel, changedMaximum: withoutMemory?.maximumAmount !== withMemory.maximumAmount, ...(withoutMemory ? { withoutMemory } : {}), withMemory, citedBlobIds: plan.citations.map((item) => item.blobId) };
|
|
186
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Sui Testnet, Walrus Testnet, and hosted Supercuts endpoints used when an owner leaves a setup field blank.
|
|
3
|
+
* None of these values are credentials; owners can still override every one in setup JSON or the prompts.
|
|
4
|
+
*/
|
|
5
|
+
export declare const TESTNET_DEFAULTS: {
|
|
6
|
+
readonly writeGrpc: "https://fullnode.testnet.sui.io:443";
|
|
7
|
+
readonly tatumGrpc: "https://sui-testnet-grpc.gateway.tatum.io:443";
|
|
8
|
+
readonly walrusPublisher: "https://publisher.walrus-testnet.walrus.space";
|
|
9
|
+
readonly walrusAggregator: "https://aggregator.walrus-testnet.walrus.space";
|
|
10
|
+
readonly directoryUrl: "https://supercuts-sellers-testnet.onrender.com";
|
|
11
|
+
};
|
|
12
|
+
/** Pinned experimental v2 Testnet deployment metadata shipped inside the npm package. */
|
|
13
|
+
export declare function bundledDeploymentConfig(): string;
|
|
14
|
+
/** Fills blank endpoint fields with the public Testnet defaults. */
|
|
15
|
+
export declare function withTestnetDefaults<T extends {
|
|
16
|
+
deploymentConfig?: string;
|
|
17
|
+
writeGrpc?: string;
|
|
18
|
+
tatumGrpc?: string;
|
|
19
|
+
walrusPublisher?: string;
|
|
20
|
+
walrusAggregator?: string;
|
|
21
|
+
}>(config: T): T;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
/**
|
|
4
|
+
* Public Sui Testnet, Walrus Testnet, and hosted Supercuts endpoints used when an owner leaves a setup field blank.
|
|
5
|
+
* None of these values are credentials; owners can still override every one in setup JSON or the prompts.
|
|
6
|
+
*/
|
|
7
|
+
export const TESTNET_DEFAULTS = {
|
|
8
|
+
writeGrpc: "https://fullnode.testnet.sui.io:443",
|
|
9
|
+
tatumGrpc: "https://sui-testnet-grpc.gateway.tatum.io:443",
|
|
10
|
+
walrusPublisher: "https://publisher.walrus-testnet.walrus.space",
|
|
11
|
+
walrusAggregator: "https://aggregator.walrus-testnet.walrus.space",
|
|
12
|
+
directoryUrl: "https://supercuts-sellers-testnet.onrender.com",
|
|
13
|
+
};
|
|
14
|
+
/** Pinned experimental v2 Testnet deployment metadata shipped inside the npm package. */
|
|
15
|
+
export function bundledDeploymentConfig() {
|
|
16
|
+
return join(dirname(fileURLToPath(import.meta.url)), "../config/phase1.testnet.json");
|
|
17
|
+
}
|
|
18
|
+
/** Fills blank endpoint fields with the public Testnet defaults. */
|
|
19
|
+
export function withTestnetDefaults(config) {
|
|
20
|
+
config.deploymentConfig ||= bundledDeploymentConfig();
|
|
21
|
+
config.writeGrpc ||= TESTNET_DEFAULTS.writeGrpc;
|
|
22
|
+
config.tatumGrpc ||= TESTNET_DEFAULTS.tatumGrpc;
|
|
23
|
+
config.walrusPublisher ||= TESTNET_DEFAULTS.walrusPublisher;
|
|
24
|
+
config.walrusAggregator ||= TESTNET_DEFAULTS.walrusAggregator;
|
|
25
|
+
return config;
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supercuts-walrus/backend",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"supercuts-seller-v2-provision": "dist/seller-v2-provision.js",
|
|
26
26
|
"supercuts-hosted-exit": "dist/hosted-exit-cli.js",
|
|
27
27
|
"supercuts-hosted-seller-import": "dist/hosted-seller-import.js",
|
|
28
|
-
"supercuts-seller-directory": "dist/seller-directory-cli.js"
|
|
28
|
+
"supercuts-seller-directory": "dist/seller-directory-cli.js",
|
|
29
|
+
"supercuts-embedding-adapter": "dist/embedding-adapter.js"
|
|
29
30
|
},
|
|
30
31
|
"files": [
|
|
31
32
|
"dist",
|