@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.
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";
3
3
  import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
4
- import { fail } from "./errors.js";
4
+ import { fail, SupercutsError } from "./errors.js";
5
5
  import { assertSuiFinality, assertTatumFinality } from "./finality.js";
6
6
  import { LivePoolIndex } from "./live-index.js";
7
7
  import { ProtocolEngine } from "./protocol-engine.js";
@@ -90,7 +90,17 @@ export class LiveRuntime {
90
90
  return new LiveRuntime(profile, store, new TatumLedger(profile.live.tatumGrpcEndpoint, tatumKey), Ed25519Keypair.fromSecretKey(decoded.secretKey), engine);
91
91
  }
92
92
  async doctor(proofsRequired = true) {
93
- const artifacts = proofsRequired ? this.prover.doctor() : { rapidSnark: false, artifacts: {} };
93
+ // Shielded proofs need separately installed artifacts; a public x402 buyer or seller stays usable without them.
94
+ let artifacts = { rapidSnark: false, artifacts: {} }, shieldedProofs = "ready", shieldedProofsReason;
95
+ try {
96
+ artifacts = this.prover.doctor();
97
+ }
98
+ catch (error) {
99
+ if (!(error instanceof SupercutsError) || !["PROVING_ARTIFACT_MISSING", "ARTIFACT_HASH_MISMATCH", "PROVER_TOOL_MISSING"].includes(error.code))
100
+ throw error;
101
+ shieldedProofs = "unavailable";
102
+ shieldedProofsReason = error.code;
103
+ }
94
104
  const [service, suiObject] = await Promise.all([this.tatum.getServiceInfo(), this.client.getObject({ objectId: this.profile.poolId, include: { json: true } })]);
95
105
  if (!suiObject.object)
96
106
  fail("POOL_OBJECT_MISSING", "pinned Testnet pool object is unavailable; check the Sui write RPC and deployment config");
@@ -98,9 +108,11 @@ export class LiveRuntime {
98
108
  fail("TATUM_UNAVAILABLE", "Tatum read RPC did not return service information; check its endpoint and owner-only API key");
99
109
  const babySeed = this.store.getSecret("identity:babyjub-seed") ?? fail("IDENTITY_MISSING", "BabyJub identity is missing");
100
110
  const identity = this.engine.identity(babySeed);
101
- const fixes = [];
102
- if (proofsRequired && !artifacts.rapidSnark)
103
- fixes.push("Set an executable RapidSnark path in the role profile for v2 proofs.");
111
+ const fixes = [], shieldedFixes = [];
112
+ if (shieldedProofs === "unavailable")
113
+ shieldedFixes.push(`Shielded payments are unavailable (${shieldedProofsReason}): install the hash-pinned v2 proving artifacts, SnarkJS, and the converter. Public x402 payments do not need them.`);
114
+ else if (!artifacts.rapidSnark)
115
+ shieldedFixes.push("Set an executable RapidSnark path in the role profile for v2 proofs.");
104
116
  if (!this.profile.live.walrusPublisherUrl)
105
117
  fixes.push("Configure the Walrus publisher URL for capability and transcript writes.");
106
118
  if (!this.profile.live.walrusAggregatorUrl)
@@ -117,7 +129,7 @@ export class LiveRuntime {
117
129
  fixes.push(`Walrus ${name} is unreachable; check the configured URL and network access.`);
118
130
  }
119
131
  }
120
- return { role: this.profile.role, network: "testnet", packageId: this.profile.packageId, poolId: this.profile.poolId, asset: this.profile.coinType, verifierVersion: this.profile.verifierVersion, keyPermissions: "owner-only", rpc: this.profile.live.writeGrpcEndpoint, healthy: true, readyForDemo: fixes.length === 0, fixes, runtime: "testnet-live", signer: this.signer.toSuiAddress(), babyJubPublicKey: identity.publicKey.map(String), x25519PublicKey: this.store.getSecret("identity:x25519-public")?.toString("base64"), memoryConfigured: Boolean(this.store.getSecret("live:memory-config")), proofsRequired, rapidSnark: artifacts.rapidSnark, artifacts: artifacts.artifacts, poolObjectVerified: true, tatumServiceVerified: true };
132
+ return { role: this.profile.role, network: "testnet", packageId: this.profile.packageId, poolId: this.profile.poolId, asset: this.profile.coinType, verifierVersion: this.profile.verifierVersion, keyPermissions: "owner-only", rpc: this.profile.live.writeGrpcEndpoint, healthy: true, readyForDemo: fixes.length === 0 && (!proofsRequired || shieldedFixes.length === 0), fixes: [...fixes, ...(proofsRequired ? shieldedFixes : [])], publicFixes: fixes, shieldedProofs, shieldedFixes, readyForPublicRail: fixes.length === 0, runtime: "testnet-live", signer: this.signer.toSuiAddress(), babyJubPublicKey: identity.publicKey.map(String), x25519PublicKey: this.store.getSecret("identity:x25519-public")?.toString("base64"), memoryConfigured: Boolean(this.store.getSecret("live:memory-config")), proofsRequired, rapidSnark: artifacts.rapidSnark, artifacts: artifacts.artifacts, poolObjectVerified: true, tatumServiceVerified: true };
121
133
  }
122
134
  async balance() {
123
135
  const state = await this.index.sync();
@@ -619,8 +631,14 @@ export class LiveRuntime {
619
631
  const paymentPayload = { x402Version: 2, resource, accepted: requirements, payload: { signature: signed.signature, transaction: Buffer.from(signed.transaction).toString("base64") } };
620
632
  const paid = await fetch(paymentUrl, { redirect: "error", headers: { "payment-signature": Buffer.from(JSON.stringify(paymentPayload)).toString("base64"), "x-supercuts-binding": Buffer.from(JSON.stringify({ ...unsignedBinding, payerSignature })).toString("base64") }, signal: AbortSignal.timeout(60_000) });
621
633
  const response = Buffer.from(await paid.arrayBuffer());
622
- if (!paid.ok)
623
- fail("X402_DELIVERY_FAILED", x402ResourceFailure(paid.status, response));
634
+ if (!paid.ok) {
635
+ const detail = x402ResourceFailure(paid.status, response);
636
+ // Settlement status is unknown: the facilitator may have broadcast before failing. Resume reuses the journaled signed
637
+ // transaction for this operation ID, and Sui executes one digest at most once, so retrying cannot pay twice.
638
+ if (paid.status >= 502 && paid.status <= 504 || /X402_FACILITATOR_UNAVAILABLE/.test(detail))
639
+ fail("X402_FACILITATOR_UNAVAILABLE", `${detail}; settlement status is unknown and the payment may already be on chain. Resume with the same operation ID: the same signed transaction is reused and cannot be paid twice`);
640
+ fail("X402_DELIVERY_FAILED", detail);
641
+ }
624
642
  const settlementHeader = paid.headers.get("payment-response") ?? fail("X402_SETTLEMENT_INVALID", "payment-response header is missing");
625
643
  let settlement;
626
644
  try {
@@ -0,0 +1,5 @@
1
+ /**
2
+ * True when this module is the process entry point. Package managers (pnpm in particular) launch bins
3
+ * through symlinks, so compare resolved real paths rather than raw `argv[1]` strings.
4
+ */
5
+ export declare function isMainModule(importMetaUrl: string, entry?: string | undefined): boolean;
@@ -0,0 +1,16 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ /**
4
+ * True when this module is the process entry point. Package managers (pnpm in particular) launch bins
5
+ * through symlinks, so compare resolved real paths rather than raw `argv[1]` strings.
6
+ */
7
+ export function isMainModule(importMetaUrl, entry = process.argv[1]) {
8
+ if (!entry)
9
+ return false;
10
+ try {
11
+ return realpathSync(entry) === realpathSync(fileURLToPath(importMetaUrl));
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
package/dist/mcp.js CHANGED
@@ -98,6 +98,8 @@ function logTool(name, data) {
98
98
  const plan = journal.step(String(data.operationId), "planning");
99
99
  if (plan)
100
100
  append("supercuts", "settlement-plan", { phase: "after-memory", planHash: plan.planHash, capabilityId: plan.capabilityId, rail: plan.rail, openingAmount: plan.openingAmount, maximumAmount: plan.maximumAmount, riskLevel: plan.riskLevel, reasonCodes: plan.reasonCodes, blobIds: plan.citations.map((item) => item.blobId) });
101
+ if (plan && evidence.memoryInfluence)
102
+ append("supercuts", "memory-influence", { planHash: plan.planHash, changedSeller: evidence.memoryInfluence.changedSeller, changedRail: evidence.memoryInfluence.changedRail, changedRisk: evidence.memoryInfluence.changedRisk, changedMaximum: evidence.memoryInfluence.changedMaximum, withoutMemory: evidence.memoryInfluence.withoutMemory, withMemory: evidence.memoryInfluence.withMemory, scorecards: evidence.memoryRanking, blobIds: evidence.memoryInfluence.citedBlobIds });
101
103
  append("supercuts", "deal-lifecycle", { dealId: data.dealId, state: data.state, rail: data.rail, agreement: data.evidence?.agreement, payment: data.evidence?.payment, delivery: data.evidence?.delivery, outcomeMemory: data.evidence?.outcomeMemory, agreementBlobId: evidence.agreementBlobId, deliveryBlobId: evidence.deliveryBlobId, outcomeMemoryBlobId: evidence.outcomeMemoryBlobId, paymentDigest: evidence.paymentDigest });
102
104
  }
103
105
  }
@@ -0,0 +1,32 @@
1
+ import type { RecalledSettlementMemory, SettlementMemoryScope, SettlementMemoryV1 } from "./settlement.js";
2
+ /** Learned view of one counterparty, derived only from trusted Walrus Memory outcomes. */
3
+ export interface SellerScorecard {
4
+ counterparty: string;
5
+ observations: number;
6
+ successes: number;
7
+ failures: number;
8
+ unresolved: number;
9
+ /** Recency-weighted Beta(1,1) posterior mean; 0.5 when nothing has been learned. */
10
+ reliability: number;
11
+ lastOutcome?: "succeeded" | "failed" | "unresolved";
12
+ lastOutcomeAt?: number;
13
+ medianLatencyMs?: number;
14
+ citedBlobIds: string[];
15
+ }
16
+ export declare const DEFAULT_HALF_LIFE_MS: number;
17
+ /** True when every field the filter names matches the memory scope. */
18
+ export declare function scopeWithin(scope: SettlementMemoryScope, filter: SettlementMemoryScope): boolean;
19
+ /**
20
+ * Trusted deal-level outcome memories for the filter, deduplicated by evidence (one deal may be recalled twice).
21
+ * Only completed deals and verified seller faults carry `signal.rail`; stage events (agreement, settlement,
22
+ * delivery) stay as evidence but are not counted as additional deals.
23
+ */
24
+ export declare function trustedOutcomes(recalled: readonly RecalledSettlementMemory[], filter: SettlementMemoryScope): Array<{
25
+ blobId: string;
26
+ memory: SettlementMemoryV1;
27
+ }>;
28
+ export declare function buildScorecard(recalled: readonly RecalledSettlementMemory[], filter: SettlementMemoryScope & {
29
+ counterparty: string;
30
+ }, now: number, halfLifeMs?: number): SellerScorecard;
31
+ /** Sum of verified paid amounts since `since` within the filter scope; a paid-but-failed deal still spent money. */
32
+ export declare function spentSince(recalled: readonly RecalledSettlementMemory[], filter: SettlementMemoryScope, since: number): bigint;
@@ -0,0 +1,76 @@
1
+ export const DEFAULT_HALF_LIFE_MS = 7 * 86_400_000;
2
+ const TRUSTED = new Set(["owner_policy", "verified_outcome"]);
3
+ /** True when every field the filter names matches the memory scope. */
4
+ export function scopeWithin(scope, filter) {
5
+ return Object.entries(filter).every(([key, value]) => value === undefined || scope[key] === value);
6
+ }
7
+ /**
8
+ * Trusted deal-level outcome memories for the filter, deduplicated by evidence (one deal may be recalled twice).
9
+ * Only completed deals and verified seller faults carry `signal.rail`; stage events (agreement, settlement,
10
+ * delivery) stay as evidence but are not counted as additional deals.
11
+ */
12
+ export function trustedOutcomes(recalled, filter) {
13
+ const seen = new Set(), result = [];
14
+ for (const item of recalled) {
15
+ const memory = item.memory;
16
+ if (!memory ||
17
+ memory.kind !== "outcome" ||
18
+ memory.signal.rail === undefined ||
19
+ !TRUSTED.has(memory.provenance) ||
20
+ !memory.signal.outcome ||
21
+ !scopeWithin(memory.scope, filter))
22
+ continue;
23
+ const key = memory.evidenceHash ?? item.blobId;
24
+ if (seen.has(key))
25
+ continue;
26
+ seen.add(key);
27
+ result.push({ blobId: item.blobId, memory });
28
+ }
29
+ return result.sort((a, b) => a.memory.createdAt - b.memory.createdAt ||
30
+ a.blobId.localeCompare(b.blobId));
31
+ }
32
+ export function buildScorecard(recalled, filter, now, halfLifeMs = DEFAULT_HALF_LIFE_MS) {
33
+ const outcomes = trustedOutcomes(recalled, filter);
34
+ let alpha = 1, beta = 1, successes = 0, failures = 0, unresolved = 0;
35
+ const latencies = [];
36
+ for (const { memory } of outcomes) {
37
+ const weight = Math.pow(0.5, Math.max(0, now - memory.createdAt) / halfLifeMs);
38
+ if (memory.signal.outcome === "succeeded") {
39
+ successes++;
40
+ alpha += weight;
41
+ }
42
+ else if (memory.signal.outcome === "failed") {
43
+ failures++;
44
+ beta += weight;
45
+ }
46
+ else {
47
+ unresolved++;
48
+ beta += weight / 2;
49
+ }
50
+ if (memory.signal.latencyMs !== undefined)
51
+ latencies.push(memory.signal.latencyMs);
52
+ }
53
+ latencies.sort((a, b) => a - b);
54
+ const last = outcomes.at(-1)?.memory;
55
+ return {
56
+ counterparty: filter.counterparty,
57
+ observations: outcomes.length,
58
+ successes,
59
+ failures,
60
+ unresolved,
61
+ reliability: alpha / (alpha + beta),
62
+ ...(last
63
+ ? { lastOutcome: last.signal.outcome, lastOutcomeAt: last.createdAt }
64
+ : {}),
65
+ ...(latencies.length
66
+ ? { medianLatencyMs: latencies[Math.floor((latencies.length - 1) / 2)] }
67
+ : {}),
68
+ citedBlobIds: outcomes.map((item) => item.blobId),
69
+ };
70
+ }
71
+ /** Sum of verified paid amounts since `since` within the filter scope; a paid-but-failed deal still spent money. */
72
+ export function spentSince(recalled, filter, since) {
73
+ return trustedOutcomes(recalled, filter)
74
+ .filter(({ memory }) => memory.signal.amount !== undefined && memory.createdAt >= since)
75
+ .reduce((total, { memory }) => total + BigInt(memory.signal.amount), 0n);
76
+ }
package/dist/memwal.d.ts CHANGED
@@ -1,5 +1,17 @@
1
1
  import { MemoryFact } from "./memory.js";
2
2
  import { type SettlementMemoryV1 } from "./settlement.js";
3
+ export declare const SIGNED_MEMORY_FORMAT: "supercuts.signed-memory.v1";
4
+ /** Sui address that signs this owner's trusted memories; derived from the owner-only memory key. */
5
+ export declare function memorySignerAddress(secret: string): string;
6
+ /** Plain-language line placed before the JSON so embeddings rank the record near scope queries. Not signed. */
7
+ export declare function describeMemory(memory: SettlementMemoryV1): string;
8
+ /** Wraps a trusted memory in a signed envelope so its authority survives a move to another machine. */
9
+ export declare function sealMemoryText(memory: SettlementMemoryV1, secret: string): string;
10
+ /** Opens recalled memory text. `signedBy` is set only when the envelope signature verifies. */
11
+ export declare function openMemoryText(text: string): {
12
+ memory: unknown;
13
+ signedBy?: string;
14
+ };
3
15
  import { RoleStore } from "./store.js";
4
16
  export interface ManualMemoryConfig {
5
17
  key: string;
@@ -15,10 +27,26 @@ export interface ManualMemoryConfig {
15
27
  walrusAggregatorUrl: string;
16
28
  walrusPublisherUrl: string;
17
29
  }
30
+ /** Public Walrus Mainnet Memory and local-adapter values; owners only need to supply their own account secrets. */
31
+ export declare const MEMORY_MAINNET_DEFAULTS: {
32
+ readonly serverUrl: "https://relayer.memory.walrus.xyz";
33
+ readonly packageId: "0xe7c16fbea0560e7057e2bf7422feaa4fb313749fc69c9e9092fac7a33b81d7f5";
34
+ readonly registryId: "0x8bf82c9e09e36b8d1c38298f68b7cb68e7b8762887e7592add9986d5e9cf199f";
35
+ readonly embeddingApiKey: "local-adapter";
36
+ readonly embeddingApiBase: "http://127.0.0.1:8787/v1";
37
+ readonly embeddingModel: "embeddinggemma-1536";
38
+ readonly walrusAggregatorUrl: "https://aggregator.walrus-mainnet.walrus.space";
39
+ readonly walrusPublisherUrl: "https://publisher.walrus-testnet.walrus.space";
40
+ };
41
+ /** Completes an owner memory config: `key`, `accountId`, and `suiPrivateKey` are required; the rest default. */
42
+ export declare function withMemoryDefaults(raw: Partial<ManualMemoryConfig>, role: string): ManualMemoryConfig;
18
43
  /** Client-managed memory facade; plaintext never crosses the relayer boundary. */
19
44
  export declare class ManualMemory {
20
45
  private readonly store;
21
46
  private readonly client;
47
+ private readonly signingKey;
48
+ /** Address whose signature marks a recalled memory as this owner's own trusted record. */
49
+ readonly signerAddress: string | undefined;
22
50
  constructor(store: RoleStore, config: ManualMemoryConfig);
23
51
  remember(fact: MemoryFact | SettlementMemoryV1): Promise<string>;
24
52
  recall(query: string, limit?: number): Promise<Array<{
package/dist/memwal.js CHANGED
@@ -1,13 +1,91 @@
1
+ import { createPrivateKey, createPublicKey, sign as edSign, verify as edVerify } from "node:crypto";
1
2
  import { MemWalManual } from "@mysten-incubation/memwal/manual";
3
+ import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";
4
+ import { Ed25519PublicKey } from "@mysten/sui/keypairs/ed25519";
2
5
  import { MemWal } from "@mysten-incubation/memwal";
3
6
  import { validateMemoryFact } from "./memory.js";
4
- import { SETTLEMENT_MEMORY_VERSION, validateSettlementMemory } from "./settlement.js";
7
+ import { canonicalJson, SETTLEMENT_MEMORY_VERSION, validateSettlementMemory } from "./settlement.js";
8
+ export const SIGNED_MEMORY_FORMAT = "supercuts.signed-memory.v1";
9
+ const PKCS8_ED25519 = Buffer.from("302e020100300506032b657004220420", "hex"), SPKI_ED25519 = Buffer.from("302a300506032b6570032100", "hex");
10
+ const memoryMessage = (memory) => Buffer.from(`supercuts-memory-v1\n${canonicalJson(memory)}`);
11
+ function memorySeed(secret) {
12
+ const decoded = decodeSuiPrivateKey(secret);
13
+ if (decoded.scheme !== "ED25519")
14
+ throw new TypeError("memory signing requires an ED25519 key");
15
+ return Buffer.from(decoded.secretKey);
16
+ }
17
+ function memoryPublicKey(secret) {
18
+ return Buffer.from(createPublicKey(createPrivateKey({ key: Buffer.concat([PKCS8_ED25519, memorySeed(secret)]), format: "der", type: "pkcs8" })).export({ format: "der", type: "spki" })).subarray(SPKI_ED25519.length);
19
+ }
20
+ /** Sui address that signs this owner's trusted memories; derived from the owner-only memory key. */
21
+ export function memorySignerAddress(secret) { return new Ed25519PublicKey(memoryPublicKey(secret)).toSuiAddress(); }
22
+ /** Plain-language line placed before the JSON so embeddings rank the record near scope queries. Not signed. */
23
+ export function describeMemory(memory) {
24
+ const scope = [memory.scope.capabilityId, memory.scope.capabilityClass, memory.scope.counterparty ? `seller ${memory.scope.counterparty}` : undefined].filter(Boolean).join(" ");
25
+ const signal = memory.signal;
26
+ const fact = memory.kind === "outcome" ? `deal ${signal.outcome ?? "recorded"}${signal.rail ? ` via ${signal.rail}` : ""}${signal.amount ? ` for ${signal.amount} MIST` : ""}${signal.latencyMs !== undefined ? ` in ${Math.round(signal.latencyMs / 1000)}s` : ""}`
27
+ : memory.kind === "privacy_rule" ? (signal.requiresShielded ? "owner requires shielded settlement" : "owner allows public settlement")
28
+ : memory.kind === "budget_limit" ? `owner budget limit ${signal.maximumAmount} MIST${signal.periodMs ? ` per ${Math.round(signal.periodMs / 3_600_000)}h` : " per deal"}`
29
+ : `${memory.kind} ${signal.reliability ?? signal.quality ?? ""}`.trim();
30
+ return `Supercuts ${memory.provenance.replace("_", " ")}: ${fact}${scope ? ` for ${scope}` : ""}.`;
31
+ }
32
+ /** Wraps a trusted memory in a signed envelope so its authority survives a move to another machine. */
33
+ export function sealMemoryText(memory, secret) {
34
+ const key = createPrivateKey({ key: Buffer.concat([PKCS8_ED25519, memorySeed(secret)]), format: "der", type: "pkcs8" });
35
+ return JSON.stringify({ format: SIGNED_MEMORY_FORMAT, summary: describeMemory(memory), memory, publicKey: memoryPublicKey(secret).toString("base64"), signature: edSign(null, memoryMessage(memory), key).toString("base64") });
36
+ }
37
+ /** Opens recalled memory text. `signedBy` is set only when the envelope signature verifies. */
38
+ export function openMemoryText(text) {
39
+ const value = JSON.parse(text);
40
+ if (!value || typeof value !== "object" || value.format !== SIGNED_MEMORY_FORMAT)
41
+ return { memory: value };
42
+ try {
43
+ const raw = Buffer.from(String(value.publicKey), "base64");
44
+ if (raw.length !== 32)
45
+ return { memory: value.memory };
46
+ const key = createPublicKey({ key: Buffer.concat([SPKI_ED25519, raw]), format: "der", type: "spki" });
47
+ return edVerify(null, memoryMessage(value.memory), key, Buffer.from(String(value.signature), "base64")) ? { memory: value.memory, signedBy: new Ed25519PublicKey(raw).toSuiAddress() } : { memory: value.memory };
48
+ }
49
+ catch {
50
+ return { memory: value.memory };
51
+ }
52
+ }
53
+ /** Public Walrus Mainnet Memory and local-adapter values; owners only need to supply their own account secrets. */
54
+ export const MEMORY_MAINNET_DEFAULTS = {
55
+ serverUrl: "https://relayer.memory.walrus.xyz",
56
+ packageId: "0xe7c16fbea0560e7057e2bf7422feaa4fb313749fc69c9e9092fac7a33b81d7f5",
57
+ registryId: "0x8bf82c9e09e36b8d1c38298f68b7cb68e7b8762887e7592add9986d5e9cf199f",
58
+ embeddingApiKey: "local-adapter",
59
+ embeddingApiBase: "http://127.0.0.1:8787/v1",
60
+ embeddingModel: "embeddinggemma-1536",
61
+ walrusAggregatorUrl: "https://aggregator.walrus-mainnet.walrus.space",
62
+ walrusPublisherUrl: "https://publisher.walrus-testnet.walrus.space",
63
+ };
64
+ /** Completes an owner memory config: `key`, `accountId`, and `suiPrivateKey` are required; the rest default. */
65
+ export function withMemoryDefaults(raw, role) {
66
+ for (const field of ["key", "accountId", "suiPrivateKey"])
67
+ if (typeof raw[field] !== "string" || !raw[field])
68
+ throw new TypeError(`memory config requires ${field}`);
69
+ return { ...MEMORY_MAINNET_DEFAULTS, namespace: `supercuts:${role}`, ...Object.fromEntries(Object.entries(raw).filter(([, value]) => value !== undefined && value !== "")) };
70
+ }
5
71
  /** Client-managed memory facade; plaintext never crosses the relayer boundary. */
6
72
  export class ManualMemory {
7
73
  store;
8
74
  client;
75
+ signingKey;
76
+ /** Address whose signature marks a recalled memory as this owner's own trusted record. */
77
+ signerAddress;
9
78
  constructor(store, config) {
10
79
  this.store = store;
80
+ try {
81
+ this.signingKey = config.suiPrivateKey;
82
+ this.signerAddress = memorySignerAddress(config.suiPrivateKey);
83
+ }
84
+ catch {
85
+ this.signingKey = undefined;
86
+ this.signerAddress = undefined;
87
+ process.stderr.write("MEMORY_SIGNING_UNAVAILABLE: trusted memories will rely on local authority only\n");
88
+ }
11
89
  let embedding;
12
90
  try {
13
91
  embedding = new URL(config.embeddingApiBase);
@@ -24,7 +102,8 @@ export class ManualMemory {
24
102
  validateSettlementMemory(fact);
25
103
  else
26
104
  validateMemoryFact(fact);
27
- const text = JSON.stringify(fact);
105
+ const trusted = fact.version === SETTLEMENT_MEMORY_VERSION && ["owner_policy", "verified_outcome"].includes(fact.provenance);
106
+ const text = trusted && this.signingKey ? sealMemoryText(fact, this.signingKey) : JSON.stringify(fact);
28
107
  const result = await this.client.rememberManual(text);
29
108
  this.store.putSecret(`memory:${result.blob_id}`, Buffer.from(text));
30
109
  return result.blob_id;
@@ -21,8 +21,9 @@ export async function onboardingDoctor(home, sellerConfig) {
21
21
  fail("LIVE_PROFILE_REQUIRED", "onboarding doctor requires a live Testnet role home");
22
22
  if (app.profile.role === "buyer") {
23
23
  const runtime = await app.execute("doctor", {});
24
- if (runtime.data.readyForDemo === false)
25
- fail("ONBOARDING_RUNTIME_NOT_READY", "live runtime doctor reports missing prerequisites");
24
+ if (runtime.data.publicFixes?.length)
25
+ fail("ONBOARDING_RUNTIME_NOT_READY", `live runtime doctor reports missing prerequisites: ${runtime.data.publicFixes.join(" ")}`);
26
+ const shieldedProofsReady = runtime.data.shieldedProofs === "ready";
26
27
  const mandate = loadMandate(home);
27
28
  if (mandate.version !== "supercuts.mandate.v2")
28
29
  fail("DEAL_MANDATE_VERSION", "buyer needs a v2 standing mandate");
@@ -48,13 +49,14 @@ export async function onboardingDoctor(home, sellerConfig) {
48
49
  ]);
49
50
  const nativeSuiMist = String(balance.balance), shieldedMist = String(shielded.data.available ?? "0");
50
51
  const checks = [
51
- { id: "runtime", status: "ready", detail: "Pinned deployment, prover, RPC, and Walrus prerequisites verified." },
52
+ { id: "runtime", status: "ready", detail: "Pinned deployment, RPC, and Walrus prerequisites verified." },
53
+ { id: "shielded-proofs", status: shieldedProofsReady ? "ready" : "unavailable", detail: shieldedProofsReady ? "Pinned v2 proving artifacts verified." : String(runtime.data.shieldedFixes?.[0] ?? "Shielded proving artifacts are not installed.") },
52
54
  { id: "public-balance", status: BigInt(nativeSuiMist) > 0n ? "ready" : "action_required", detail: BigInt(nativeSuiMist) > 0n ? `${nativeSuiMist} MIST available.` : "Fund only this generated buyer address before a public payment." },
53
55
  { id: "shielded-balance", status: BigInt(shieldedMist) > 0n ? "ready" : "action_required", detail: BigInt(shieldedMist) > 0n ? `${shieldedMist} MIST available in the experimental pool.` : "Create a buyer-owned pool deposit before a shielded deal." },
54
56
  { id: "seller-approval", status: mandate.allowedCounterparties.length > 0 ? "ready" : "action_required", detail: mandate.allowedCounterparties.length > 0 ? `${mandate.allowedCounterparties.length} seller identity or identities approved.` : "Review and approve a signed seller capability locally." },
55
57
  ...(directoryListings !== undefined ? [{ id: "directory", status: directoryListings > 0 ? "ready" : "action_required", detail: `${directoryListings} live directory listing or listings.` }] : []),
56
58
  ];
57
- return { role: "buyer", healthy: true, readyForPublicPayment: BigInt(nativeSuiMist) > 0n && mandate.allowedCounterparties.length > 0, readyForShieldedPayment: BigInt(shieldedMist) > 0n && mandate.allowedCounterparties.length > 0, runtime: runtime.data, checks, approvedSellers: mandate.allowedCounterparties.length, nativeSuiMist, shieldedMist, funded: BigInt(nativeSuiMist) > 0n, ...(directoryListings !== undefined ? { directoryListings } : {}) };
59
+ return { role: "buyer", healthy: true, readyForPublicPayment: BigInt(nativeSuiMist) > 0n && mandate.allowedCounterparties.length > 0, readyForShieldedPayment: shieldedProofsReady && BigInt(shieldedMist) > 0n && mandate.allowedCounterparties.length > 0, runtime: runtime.data, checks, approvedSellers: mandate.allowedCounterparties.length, nativeSuiMist, shieldedMist, funded: BigInt(nativeSuiMist) > 0n, ...(directoryListings !== undefined ? { directoryListings } : {}) };
58
60
  }
59
61
  if (app.profile.role === "seller") {
60
62
  if (!sellerConfig)
@@ -81,7 +83,7 @@ export async function onboardingDoctor(home, sellerConfig) {
81
83
  }
82
84
  const runtime = await app.execute("doctor", { "proofs-required": proofsRequired });
83
85
  if (runtime.data.readyForDemo === false)
84
- fail("ONBOARDING_RUNTIME_NOT_READY", "live runtime doctor reports missing prerequisites");
86
+ fail("ONBOARDING_RUNTIME_NOT_READY", `live runtime doctor reports missing prerequisites: ${(runtime.data.fixes ?? []).join(" ")}`);
85
87
  return { role: "seller", healthy: true, runtime: runtime.data, capabilities: config.capabilities.length, origin: config.origin };
86
88
  }
87
89
  return fail("ROLE_FORBIDDEN", "onboarding doctor supports buyer and seller homes");
package/dist/phase3.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import { RoleStore } from "./store.js";
2
2
  import { type LiveRuntimeProfile } from "./live-runtime.js";
3
+ import { type CapabilityManifest, type CapabilityRail } from "./capability.js";
4
+ import { type SellerScorecard } from "./memory-scorecard.js";
5
+ import { type MemoryInfluence, type PlanningMandate, type SettlementPlanV1 } from "./settlement.js";
3
6
  export type Role = "buyer" | "seller" | "auditor";
4
7
  export interface Profile {
5
8
  version: 1 | 2;
@@ -63,12 +66,52 @@ export declare class Phase3App {
63
66
  private storedPlan;
64
67
  private persistPlan;
65
68
  private recalledMockMemories;
69
+ /** Trusted provenance needs local authority or this owner's memory-key signature (portable across machines). */
66
70
  private authenticatedMemory;
67
71
  private registerMemoryAuthority;
68
72
  private planSettlement;
73
+ /** Recall settlement memories for several scoped queries; plaintext stays in this process. */
74
+ private recallSettlementMemories;
75
+ /**
76
+ * Verified outcomes this runtime wrote itself, including ones still queued for Walrus. They are authoritative
77
+ * for spend and reliability on this machine; semantic recall alone can miss or lag them.
78
+ */
79
+ private localVerifiedOutcomes;
80
+ /**
81
+ * Orders candidate sellers by what Walrus Memory has verified about them, instead of by price alone.
82
+ * Sellers outside the owner mandate are dropped first so memory can never widen authority.
83
+ */
84
+ rankDealCandidates(operationId: string, candidates: CapabilityManifest[], mandate: PlanningMandate, request?: {
85
+ budgetMaximum?: string;
86
+ requestedRail?: CapabilityRail;
87
+ privacyHint?: "any" | "shielded";
88
+ }): Promise<{
89
+ ordered: CapabilityManifest[];
90
+ scorecards: Array<SellerScorecard & {
91
+ capabilityId: string;
92
+ priceMinimum: string;
93
+ }>;
94
+ }>;
95
+ /** Stores the counterfactual (same mandate, no memory, every candidate) next to the binding deal plan. */
96
+ recordDealMemoryInfluence(dealOperationId: string, plan: SettlementPlanV1, mandate: PlanningMandate, request?: {
97
+ requestedRail?: CapabilityRail;
98
+ privacyHint?: "any" | "shielded";
99
+ }): MemoryInfluence | undefined;
100
+ memoryInfluence(id: string): MemoryInfluence | undefined;
101
+ /**
102
+ * Owner view of what Supercuts has learned: fresh scorecards recalled from Walrus Memory for every seller
103
+ * it has ranked, plus how often memory changed a payment decision. Counts and blob IDs only, never plaintext.
104
+ */
105
+ memoryInsights(): Promise<Record<string, unknown>>;
69
106
  private typedMemory;
70
107
  private rememberTyped;
71
108
  private enqueueVerifiedOutcome;
109
+ /** Queues a verified seller fault as a Walrus outcome memory so later discovery demotes that seller. */
110
+ recordVerifiedDealFailure(operationId: string, planHash: string, fault: {
111
+ stage: string;
112
+ code: string;
113
+ paymentDigest?: string;
114
+ }): void;
72
115
  recordVerifiedDealOutcome(operationId: string, planHash: string, paymentDigest: string, artifactSha256: string): {
73
116
  payloadHash: string;
74
117
  queued: true;