@supercuts-walrus/backend 0.1.1 → 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/README.md +28 -5
- package/config/phase1.testnet.json +20 -0
- package/config/proving-manifest.json +11 -0
- package/dist/buyer-setup.js +27 -13
- package/dist/cli.js +10 -2
- package/dist/dashboard.js +1 -1
- package/dist/deal-adapter.js +0 -2
- package/dist/deal-engine.d.ts +13 -0
- package/dist/deal-engine.js +64 -39
- package/dist/deal.d.ts +35 -0
- package/dist/deal.js +9 -3
- 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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/live-deal-ports.d.ts +2 -1
- package/dist/live-deal-ports.js +31 -5
- package/dist/live-runtime.d.ts +1 -1
- package/dist/live-runtime.js +27 -11
- package/dist/live-seller-transport.js +5 -1
- package/dist/main-module.d.ts +5 -0
- package/dist/main-module.js +16 -0
- package/dist/mandate.js +1 -1
- package/dist/mcp-bootstrap.js +3 -3
- 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 +12 -5
- package/dist/phase3.d.ts +43 -0
- package/dist/phase3.js +147 -32
- package/dist/role-lock.d.ts +1 -1
- package/dist/role-lock.js +1 -1
- package/dist/seam.js +2 -1
- package/dist/seller-catalog-generate.js +4 -4
- package/dist/seller-directory-cli.js +2 -1
- package/dist/seller-live-cli.js +10 -1
- package/dist/seller-provision.js +15 -1
- package/dist/seller-research.d.ts +50 -0
- package/dist/seller-research.js +210 -0
- package/dist/seller-scaffold.d.ts +1 -0
- package/dist/seller-scaffold.js +35 -0
- package/dist/seller-service.d.ts +1 -1
- package/dist/seller-service.js +8 -20
- package/dist/seller-setup.d.ts +3 -0
- package/dist/seller-setup.js +109 -0
- 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 +4 -2
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ bytes under that operation ID; retrying another payload with the same ID fails.
|
|
|
77
77
|
|
|
78
78
|
## Procurement demo
|
|
79
79
|
|
|
80
|
-
For the Codex buyer with three remote Claude-
|
|
80
|
+
For the Codex buyer with three remote sellers, Claude-researched and owner-reviewed artifacts, Supabase-backed seller state, and the separate Render facilitator, follow [the remote demo guide](../docs/remote-codex-demo.md). The remote path is live-only and gives Codex a buyer role MCP server; the three seller panes use the safe `supercuts-seller-watch` event stream.
|
|
81
81
|
|
|
82
82
|
`supercuts-demo` is the operator entry point:
|
|
83
83
|
|
|
@@ -116,9 +116,11 @@ For a Claude-agent recording, export `CLAUDE_API_KEY` or provide
|
|
|
116
116
|
`live --claude-key-file <owner-only-file>`; a repository `.env` file is not
|
|
117
117
|
loaded automatically. Validate the credential against Anthropic before starting
|
|
118
118
|
a funded take.
|
|
119
|
-
The
|
|
120
|
-
only the model, action, and token counts.
|
|
121
|
-
|
|
119
|
+
The recorded workflow can call Claude for bounded buyer decisions and records
|
|
120
|
+
only the model, action, and token counts. Hosted seller price and agreement
|
|
121
|
+
decisions are deterministic after backend validation; Claude web research runs
|
|
122
|
+
before provisioning so model availability cannot interrupt a paid request. The
|
|
123
|
+
backend still enforces capability, amount, rail, and agreement bindings. Validate that recording with
|
|
122
124
|
`supercuts-demo check --require-claude`; an earlier funded run without these
|
|
123
125
|
agent-decision events does not satisfy that gate.
|
|
124
126
|
|
|
@@ -185,7 +187,28 @@ signer, pool notes, Walrus and Walrus Memory configuration. A missing file
|
|
|
185
187
|
returns `DEAL_SELLERS_NOT_CONFIGURED`. An owner-only `deal-ports.mjs` exporting
|
|
186
188
|
`createDealPorts(app, mandate)` remains available as an advanced override.
|
|
187
189
|
|
|
188
|
-
|
|
190
|
+
Create a seller home with `supercuts onboard seller --home <seller-home>` or
|
|
191
|
+
its noninteractive `--config <owner-only-setup.json>` form. It generates a
|
|
192
|
+
fresh seller signer by default, writes a seller MCP stanza, and explicitly
|
|
193
|
+
reports that the seller should not receive faucet funds. The seller service is
|
|
194
|
+
`supercuts-seller-live <seller-home> <config.json>`.
|
|
195
|
+
Use `supercuts seller scaffold` to generate the offer spec, replaceable handler,
|
|
196
|
+
future expiry, environment-based facilitator config, and exact next commands.
|
|
197
|
+
The npm tarball includes pinned metadata for x402-only sellers; shielded sellers
|
|
198
|
+
still install the proof artifacts required for recovery and exits.
|
|
199
|
+
|
|
200
|
+
Research sellers can create a source-backed draft before packaging:
|
|
201
|
+
|
|
202
|
+
```sh
|
|
203
|
+
CLAUDE_API_KEY=... supercuts seller research --kind market-brief \
|
|
204
|
+
--topic "Current Sui ecosystem market structure and risks" \
|
|
205
|
+
--output <owner-only-draft.json>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The command uses Anthropic web search, rejects citations absent from the search
|
|
209
|
+
evidence, and writes a mode-0600 draft marked `draft-needs-owner-review`.
|
|
210
|
+
Optional `--allowed-domains` constrains sources. It never publishes or changes
|
|
211
|
+
live seller state; an owner must review and approve the immutable bytes first.
|
|
189
212
|
Register each capability first with
|
|
190
213
|
`supercuts-seller-register <seller-home> <spec.json> <registration.json>`.
|
|
191
214
|
The spec contains `capabilityId`, `capability`, `description`, `inputSchema`,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"network": "testnet",
|
|
3
|
+
"sender": "0x66fdfe84ff521627c93910c93ec0bc26934ee1fef3a1a0a5ce14b7dc4567ec00",
|
|
4
|
+
"coinType": "0x2::sui::SUI",
|
|
5
|
+
"coinTypeHash": "be4507e04f555448ca05c0c9f8345c5ea9f3e40e8616a20f5d5c18b40ef399eb",
|
|
6
|
+
"packageId": "0x32d104c1ab422d497df4ae3a6861258694949c86428f886ca338e07bd055cf76",
|
|
7
|
+
"poolId": "0x8fd22140c906495bb5334a497dcdd9112479c638e21c5df4b4cfb66f633a106b",
|
|
8
|
+
"poolCreateTransaction": "BnzMqokgQVWwSkRYCCrMTyWwHN6HVdDKWwVDJXTZqC6b",
|
|
9
|
+
"deploymentCheckpoint": "401762744",
|
|
10
|
+
"deploymentDomain": "3082f331660eb38075e250a810a701bc4b0de2ca329eda70b2d76e3795517305",
|
|
11
|
+
"initialRoot": "2134e76ac5d21aab186c2be1dd8f84ee880a1e46eaf712f9d371b6df22191f3e",
|
|
12
|
+
"verifierVersion": 2,
|
|
13
|
+
"testOnly": true,
|
|
14
|
+
"artifactManifest": "config/proving-manifest.json",
|
|
15
|
+
"artifacts": {
|
|
16
|
+
"deposit": { "r1csSha256": "f36a1a18bf9d243e089e6ab14436e5a921d2b839d81bea95db319a4cdb80a197", "zkeySha256": "d398776a54628c390e8bf822007e47ed8fac00e92e8ea95bb17d20db517998dc", "verifyingKeySha256": "838404ff0ae3a9fc82da0856b68a11428cf3ffef335b4c6539fa1860d8e1ba26" },
|
|
17
|
+
"transfer": { "r1csSha256": "055ea2ec8f689c48e59f89fe06db6fb980dc575982214bfe60f675e8f08485c3", "zkeySha256": "6e7bcac98b8d2a78fd1811e2caadd83d0d6a301951f8cf27ac55a67006b001c2", "verifyingKeySha256": "8a6896f3f330769aa69e0dc1251a19fb69c4d6264b5729d0104df8d262e573ba" },
|
|
18
|
+
"withdraw": { "r1csSha256": "85cf1542ab2f2ece4bbd8757def48d526ba0951a832a2e0ebbf97ace2c611308", "zkeySha256": "fd303dd0dd53b9b752dac0de546dabdd03198a7a4defde5e8d6018bccd419c56", "verifyingKeySha256": "445c1982bea5e465fb0f5665023bb279637d820a9425f16a4c2f2ee7460e7e4a" }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "supercuts-pool-v2",
|
|
3
|
+
"testOnly": true,
|
|
4
|
+
"warning": "Per-circuit contributions are locally generated and unaudited; never use these keys with production assets.",
|
|
5
|
+
"powersOfTauSha256": "3f428d1a407e4704ef906960e000b03089e5e6ec29bf65b07bb5e3de005f4700",
|
|
6
|
+
"circuits": {
|
|
7
|
+
"deposit": { "r1csSha256": "f36a1a18bf9d243e089e6ab14436e5a921d2b839d81bea95db319a4cdb80a197", "zkeySha256": "d398776a54628c390e8bf822007e47ed8fac00e92e8ea95bb17d20db517998dc", "verificationKeySha256": "838404ff0ae3a9fc82da0856b68a11428cf3ffef335b4c6539fa1860d8e1ba26", "proofSha256": "9b7835852cd9ced7c02828e5c563191ac597f7175b04fa9bb01c66cc93a93e58", "publicInputsSha256": "23569e5994ddd08b555dcbcf12c735ec303e000f6e7e90c9b63667dec60b6aeb" },
|
|
8
|
+
"transfer": { "r1csSha256": "055ea2ec8f689c48e59f89fe06db6fb980dc575982214bfe60f675e8f08485c3", "zkeySha256": "6e7bcac98b8d2a78fd1811e2caadd83d0d6a301951f8cf27ac55a67006b001c2", "verificationKeySha256": "8a6896f3f330769aa69e0dc1251a19fb69c4d6264b5729d0104df8d262e573ba", "proofSha256": "ff9a902e91e7d7b64e278cc7e17c22ad3f031f350aacb43f610771adafbf3f35", "publicInputsSha256": "903861e7ee9f792a17194f96649736d4916f45605918acac78e5d40a5b8f6170" },
|
|
9
|
+
"withdraw": { "r1csSha256": "85cf1542ab2f2ece4bbd8757def48d526ba0951a832a2e0ebbf97ace2c611308", "zkeySha256": "fd303dd0dd53b9b752dac0de546dabdd03198a7a4defde5e8d6018bccd419c56", "verificationKeySha256": "445c1982bea5e465fb0f5665023bb279637d820a9425f16a4c2f2ee7460e7e4a", "proofSha256": "4746ee54c04389929aad48c0c5a0cf18985b9dd6bcd7a3b83aa717d8847973d3", "publicInputsSha256": "d18d5852ac3c636c7b081bc52d367c896c95bd5a89feacc91014afefe3dcff54" }
|
|
10
|
+
}
|
|
11
|
+
}
|
package/dist/buyer-setup.js
CHANGED
|
@@ -14,6 +14,7 @@ import { verifySignedCapability } from "./capability.js";
|
|
|
14
14
|
import { WalrusDealBlobs } from "./deal-wire.js";
|
|
15
15
|
import { canonicalJson } from "./settlement.js";
|
|
16
16
|
import { validateDirectoryOrigin } from "./seller-directory.js";
|
|
17
|
+
import { TESTNET_DEFAULTS, withTestnetDefaults } from "./testnet-defaults.js";
|
|
17
18
|
function ownerFile(path) {
|
|
18
19
|
if ((statSync(path).mode & 0o077) !== 0)
|
|
19
20
|
fail("SETUP_PERMISSIONS", `${path} must be owner-only`);
|
|
@@ -34,6 +35,9 @@ function origins(values) {
|
|
|
34
35
|
});
|
|
35
36
|
}
|
|
36
37
|
function validateConfig(config) {
|
|
38
|
+
withTestnetDefaults(config);
|
|
39
|
+
if (!config.sellerOrigins?.length && !config.directoryUrl)
|
|
40
|
+
config.directoryUrl = TESTNET_DEFAULTS.directoryUrl;
|
|
37
41
|
amount(config.perDealCeiling, "perDealCeiling");
|
|
38
42
|
amount(config.totalBudget, "totalBudget");
|
|
39
43
|
if (BigInt(config.totalBudget) < BigInt(config.perDealCeiling))
|
|
@@ -48,10 +52,10 @@ function validateConfig(config) {
|
|
|
48
52
|
fail("SETUP_INVALID", "configure a directoryUrl or sellerOrigins");
|
|
49
53
|
if (config.directoryUrl)
|
|
50
54
|
origins([config.directoryUrl]);
|
|
51
|
-
for (const key of ["deploymentConfig", "tatumKeyFile", "writeGrpc", "tatumGrpc", "walrusPublisher", "walrusAggregator"
|
|
55
|
+
for (const key of ["deploymentConfig", "tatumKeyFile", "writeGrpc", "tatumGrpc", "walrusPublisher", "walrusAggregator"])
|
|
52
56
|
if (typeof config[key] !== "string" || !config[key])
|
|
53
57
|
fail("SETUP_INVALID", `${key} is required`);
|
|
54
|
-
for (const path of [config.tatumKeyFile, config.memoryConfigFile, ...(config.suiKeyFile ? [config.suiKeyFile] : [])])
|
|
58
|
+
for (const path of [config.tatumKeyFile, ...(config.memoryConfigFile ? [config.memoryConfigFile] : []), ...(config.suiKeyFile ? [config.suiKeyFile] : [])])
|
|
55
59
|
ownerFile(resolve(path));
|
|
56
60
|
const deployment = JSON.parse(readFileSync(resolve(config.deploymentConfig), "utf8"));
|
|
57
61
|
if (deployment.network !== "testnet" || deployment.verifierVersion !== 2)
|
|
@@ -86,22 +90,25 @@ export async function promptBuyerSetup(home) {
|
|
|
86
90
|
fail("SETUP_INPUT_REQUIRED", "interactive setup requires a terminal; use --config with an owner-only JSON file");
|
|
87
91
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
88
92
|
try {
|
|
89
|
-
const ask = async (label) => (await rl.question(`${label}: `)).trim();
|
|
90
|
-
|
|
93
|
+
const ask = async (label, fallback) => (await rl.question(fallback ? `${label} [${fallback}]: ` : `${label}: `)).trim() || fallback || "";
|
|
94
|
+
process.stderr.write("Press Enter to accept a bracketed default. Credentials are read from owner-only files, never typed here.\n");
|
|
95
|
+
const days = Number(await ask("Policy duration in days", "14"));
|
|
91
96
|
if (!Number.isSafeInteger(days) || days < 1 || days > 365)
|
|
92
97
|
fail("SETUP_INVALID", "policy duration must be 1..365 days");
|
|
93
98
|
const config = {
|
|
94
|
-
deploymentConfig: await ask("Pinned Testnet deployment config path"), tatumKeyFile: await ask("Tatum key file path"),
|
|
95
|
-
writeGrpc: await ask("Sui write gRPC URL"), tatumGrpc: await ask("Tatum read gRPC URL"),
|
|
96
|
-
walrusPublisher: await ask("Walrus publisher URL"), walrusAggregator: await ask("Walrus aggregator URL"),
|
|
97
|
-
memoryConfigFile: await ask("Owner-only Walrus Memory config path"),
|
|
98
|
-
directoryUrl: await ask("Shared directory origin (
|
|
99
|
+
deploymentConfig: await ask("Pinned Testnet deployment config path", "bundled"), tatumKeyFile: await ask("Owner-only Tatum API key file path"),
|
|
100
|
+
writeGrpc: await ask("Sui write gRPC URL", TESTNET_DEFAULTS.writeGrpc), tatumGrpc: await ask("Tatum read gRPC URL", TESTNET_DEFAULTS.tatumGrpc),
|
|
101
|
+
walrusPublisher: await ask("Walrus publisher URL", TESTNET_DEFAULTS.walrusPublisher), walrusAggregator: await ask("Walrus aggregator URL", TESTNET_DEFAULTS.walrusAggregator),
|
|
102
|
+
memoryConfigFile: await ask("Owner-only Walrus Memory config path (blank to skip Memory)"),
|
|
103
|
+
directoryUrl: await ask("Shared directory origin (type none for direct sellers only)", TESTNET_DEFAULTS.directoryUrl),
|
|
99
104
|
sellerOrigins: (await ask("Additional seller HTTPS origins, comma separated (or blank)")).split(",").map((item) => item.trim()).filter(Boolean),
|
|
100
105
|
allowedCounterparties: (await ask("Initially approved seller Sui addresses, comma separated (or blank)")).split(",").map((item) => item.trim()).filter(Boolean),
|
|
101
|
-
perDealCeiling: await ask("Maximum per deal in MIST"), totalBudget: await ask("Lifetime cumulative budget in MIST"),
|
|
106
|
+
perDealCeiling: await ask("Maximum per deal in MIST", "2000000"), totalBudget: await ask("Lifetime cumulative budget in MIST", "10000000"),
|
|
102
107
|
expiresAt: Date.now() + days * 24 * 60 * 60 * 1000, allowPublicX402: (await ask("Allow public x402 when requested? yes/no")).toLowerCase() === "yes",
|
|
103
108
|
};
|
|
104
|
-
if (
|
|
109
|
+
if (config.deploymentConfig === "bundled")
|
|
110
|
+
config.deploymentConfig = "";
|
|
111
|
+
if (!config.directoryUrl || config.directoryUrl === "none")
|
|
105
112
|
delete config.directoryUrl;
|
|
106
113
|
return setupBuyerConfig(home, validateConfig(config));
|
|
107
114
|
}
|
|
@@ -118,13 +125,20 @@ function setupBuyerConfig(home, config) {
|
|
|
118
125
|
if (!config.suiKeyFile)
|
|
119
126
|
writeFileSync(keyPath, Ed25519Keypair.generate().getSecretKey(), { mode: 0o600, flag: "wx" });
|
|
120
127
|
ownerFile(keyPath);
|
|
121
|
-
initialize(home, { role: "buyer", "deployment-config": resolve(config.deploymentConfig), "sui-key-file": keyPath, "tatum-key-file": resolve(config.tatumKeyFile), "write-grpc": config.writeGrpc, "tatum-grpc": config.tatumGrpc, "walrus-publisher": config.walrusPublisher, "walrus-aggregator": config.walrusAggregator, "memory-config-file": resolve(config.memoryConfigFile), "allow-counterparty": (config.allowedCounterparties ?? []).join(","), "max-amount": config.perDealCeiling, ...(config.rapidsnark ? { rapidsnark: resolve(config.rapidsnark) } : {}) });
|
|
128
|
+
initialize(home, { role: "buyer", "deployment-config": resolve(config.deploymentConfig), "sui-key-file": keyPath, "tatum-key-file": resolve(config.tatumKeyFile), "write-grpc": config.writeGrpc, "tatum-grpc": config.tatumGrpc, "walrus-publisher": config.walrusPublisher, "walrus-aggregator": config.walrusAggregator, ...(config.memoryConfigFile ? { "memory-config-file": resolve(config.memoryConfigFile) } : {}), "allow-counterparty": (config.allowedCounterparties ?? []).join(","), "max-amount": config.perDealCeiling, ...(config.rapidsnark ? { rapidsnark: resolve(config.rapidsnark) } : {}) });
|
|
122
129
|
writePolicy(home, config);
|
|
123
130
|
const session = createBuyerSession(home);
|
|
124
131
|
const decoded = decodeSuiPrivateKey(readFileSync(keyPath, "utf8").trim());
|
|
125
132
|
if (decoded.scheme !== "ED25519")
|
|
126
133
|
fail("SETUP_INVALID", "buyer key must use ED25519");
|
|
127
|
-
|
|
134
|
+
const address = Ed25519Keypair.fromSecretKey(decoded.secretKey).toSuiAddress();
|
|
135
|
+
return { home, address, ...session, policy: "configured", nextSteps: [
|
|
136
|
+
`Fund only this buyer with Testnet SUI: supercuts funding execute --home ${home} --public-payment <MIST> --shielded-payment 0 --operation-id fund-1 --request-faucet --confirm (or use https://faucet.sui.io for ${address})`,
|
|
137
|
+
...(config.allowedCounterparties?.length ? [] : [`Review the directory, then approve a seller: supercuts policy approve-seller --home ${home} --capability-id <id>`]),
|
|
138
|
+
...(config.memoryConfigFile ? [`Keep the Walrus Memory embedding adapter running (supercuts-embedding-adapter). An MCP agent flushes outcome memories itself; SDK-only hosts also run: supercuts-memory-worker ${home}`] : []),
|
|
139
|
+
`Check readiness: supercuts onboarding doctor --home ${home}`,
|
|
140
|
+
`Connect an agent with ${session.mcpConfig}, or call BuyerClient.executeDeal from @supercuts-walrus/sdk.`,
|
|
141
|
+
] };
|
|
128
142
|
}
|
|
129
143
|
export function createBuyerSession(home) {
|
|
130
144
|
if (!existsSync(join(home, "profile.json")))
|
package/dist/cli.js
CHANGED
|
@@ -4,19 +4,27 @@ import { addBuyerPrivacyRule, approveBuyerSeller, createBuyerSession, promptBuye
|
|
|
4
4
|
import { onboardingDoctor } from "./onboarding-doctor.js";
|
|
5
5
|
import { executeFunding, prepareFunding } from "./funding.js";
|
|
6
6
|
import { packageHostedSeller } from "./seller-package.js";
|
|
7
|
+
import { promptSellerSetup, setupSeller } from "./seller-setup.js";
|
|
8
|
+
import { scaffoldSeller } from "./seller-scaffold.js";
|
|
9
|
+
import { generateSellerResearch } from "./seller-research.js";
|
|
7
10
|
const words = process.argv.slice(2);
|
|
8
11
|
let command = words.shift() ?? "";
|
|
9
12
|
if (["wallet", "offer", "pay", "resource", "memory", "audit", "capability", "settlement", "setup", "policy", "onboarding", "onboard", "funding", "seller"].includes(command))
|
|
10
13
|
command += `.${words.shift() ?? ""}`;
|
|
11
14
|
try {
|
|
12
15
|
if (!command || command === "help" || command === "--help" || command === "setup.") {
|
|
13
|
-
process.stdout.write(`${JSON.stringify({ schemaVersion: "supercuts.help.v1", ok: true, commands: ["onboard buyer", "funding prepare", "funding execute", "
|
|
16
|
+
process.stdout.write(`${JSON.stringify({ schemaVersion: "supercuts.help.v1", ok: true, commands: ["onboard buyer", "onboard seller", "seller scaffold", "seller research", "seller package", "funding prepare", "funding execute", "setup buyer", "setup seller", "setup renew", "setup session", "init", "doctor", "status", "wallet", "offer", "pay", "memory", "memory insights", "capability", "audit"], setup: "Run supercuts onboard buyer or supercuts onboard seller with an isolated --home. Then use seller scaffold to generate a capability. Research sellers can use seller research to create a cited owner-review draft before packaging.", live: "Fund only the generated buyer address. A seller does not need faucet funds to publish or receive a capability payment." })}\n`);
|
|
14
17
|
process.exit(0);
|
|
15
18
|
}
|
|
16
19
|
const args = parseArgs(words);
|
|
17
20
|
const home = homePath(args);
|
|
18
21
|
const fundingInput = { publicPayment: String(args["public-payment"] ?? "0"), shieldedPayment: String(args["shielded-payment"] ?? "0"), ...(typeof args["gas-reserve"] === "string" ? { gasReserve: args["gas-reserve"] } : {}), ...(typeof args["change-reserve"] === "string" ? { changeReserve: args["change-reserve"] } : {}) };
|
|
19
|
-
const result = command === "init" ? initialize(home, args) : command === "setup.buyer" || command === "onboard.buyer" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: typeof args.config === "string" ? setupBuyer(home, args.config) : await promptBuyerSetup(home) } : command === "setup.renew" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: renewBuyer(home, String(args.config ?? "")) } : command === "setup.session" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: createBuyerSession(home) } : command === "funding.prepare" ? { schemaVersion: "supercuts.funding.v1", ok: true, command, data: await prepareFunding(home, fundingInput) } : command === "funding.execute" ? { schemaVersion: "supercuts.funding.v1", ok: true, command, data: await executeFunding(home, { ...fundingInput, operationId: String(args["operation-id"] ?? ""), requestFaucet: args["request-faucet"] === true, confirmed: args.confirm === true }) } : command === "seller.package" ? { schemaVersion: "supercuts.seller-package.v1", ok: true, command, data: await packageHostedSeller({ home, spec: String(args.spec ?? ""), artifact: String(args.artifact ?? ""), metadata: String(args.metadata ?? ""), registration: String(args.registration ?? ""), bundle: String(args.bundle ?? ""), ...(typeof args["content-type"] === "string" ? { contentType: args["content-type"] } : {}) }) } : command === "policy.add" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: await addBuyerPrivacyRule(home, String(args["capability-id"] ?? "")) } : command === "policy.approve-seller" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: await approveBuyerSeller(home, String(args["capability-id"] ?? "")) } : command === "
|
|
22
|
+
const result = command === "init" ? initialize(home, args) : command === "setup.buyer" || command === "onboard.buyer" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: typeof args.config === "string" ? setupBuyer(home, args.config) : await promptBuyerSetup(home) } : command === "setup.seller" || command === "onboard.seller" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: typeof args.config === "string" ? setupSeller(home, args.config) : await promptSellerSetup(home) } : command === "seller.scaffold" ? { schemaVersion: "supercuts.seller-scaffold.v1", ok: true, command, data: scaffoldSeller({ ...args, home }) } : command === "seller.research" ? { schemaVersion: "supercuts.seller-research.v1", ok: true, command, data: await generateSellerResearch({ kind: String(args.kind ?? ""), topic: String(args.topic ?? ""), output: String(args.output ?? ""), ...(typeof args["context-file"] === "string" ? { contextFile: args["context-file"] } : {}), ...(typeof args["allowed-domains"] === "string" ? { allowedDomains: args["allowed-domains"] } : {}), ...(typeof args.model === "string" ? { model: args.model } : {}) }) } : command === "setup.renew" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: renewBuyer(home, String(args.config ?? "")) } : command === "setup.session" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: createBuyerSession(home) } : command === "funding.prepare" ? { schemaVersion: "supercuts.funding.v1", ok: true, command, data: await prepareFunding(home, fundingInput) } : command === "funding.execute" ? { schemaVersion: "supercuts.funding.v1", ok: true, command, data: await executeFunding(home, { ...fundingInput, operationId: String(args["operation-id"] ?? ""), requestFaucet: args["request-faucet"] === true, confirmed: args.confirm === true }) } : command === "seller.package" ? { schemaVersion: "supercuts.seller-package.v1", ok: true, command, data: await packageHostedSeller({ home, spec: String(args.spec ?? ""), artifact: String(args.artifact ?? ""), metadata: String(args.metadata ?? ""), registration: String(args.registration ?? ""), bundle: String(args.bundle ?? ""), ...(typeof args["content-type"] === "string" ? { contentType: args["content-type"] } : {}) }) } : command === "policy.add" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: await addBuyerPrivacyRule(home, String(args["capability-id"] ?? "")) } : command === "policy.approve-seller" ? { schemaVersion: "supercuts.setup.v1", ok: true, command, data: await approveBuyerSeller(home, String(args["capability-id"] ?? "")) } : command === "memory.insights" ? await (async () => { const app = new Phase3App(home); try {
|
|
23
|
+
return { schemaVersion: "supercuts.memory-insights.v1", ok: true, command, data: await app.memoryInsights() };
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
app.close();
|
|
27
|
+
} })() : command === "onboarding.doctor" ? { schemaVersion: "supercuts.onboarding.v1", ok: true, command, data: await onboardingDoctor(home, typeof args.config === "string" ? args.config : undefined) } : await (async () => { const app = new Phase3App(home); try {
|
|
20
28
|
return await app.execute(command, args);
|
|
21
29
|
}
|
|
22
30
|
finally {
|
package/dist/dashboard.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
2
|
import { readDemoEvents, recordedReplay } from "./events.js";
|
|
3
|
-
const html = `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Supercuts funded-run evidence</title><style>body{font:15px system-ui;margin:0;background:#0b1020;color:#eef2ff}header{position:sticky;top:0;padding:18px 24px;background:#111936;border-bottom:1px solid #334155}main{max-width:980px;margin:auto;padding:24px}.event{display:grid;grid-template-columns:9rem 10rem 1fr;gap:14px;padding:14px;border-bottom:1px solid #27334f}.mode{color:#fbbf24;font-weight:700}.complete,.allowed{color:#86efac}.failed,.rejected{color:#fca5a5}code{color:#bfdbfe;white-space:pre-wrap}</style></head><body><header><b>Supercuts</b> · <span class="mode" id="mode">loading evidence…</span></header><main id="events"></main><script>const out=document.querySelector('#events');const source=new EventSource('/events');source.onmessage=e=>{const v=JSON.parse(e.data);document.querySelector('#mode').textContent=v.mode==='live'?'LIVE FUNDED RUN':'RECORDED FUNDED RUN';const n=document.createElement('div');n.className='event';n.innerHTML='<span>#'+v.sequence+' '+v.actor+'</span><span class="'+v.status+'">'+v.stage+' · '+v.status+'</span><code></code>';n.querySelector('code').textContent=JSON.stringify(v.publicDetails,null,2);out.append(n)};</script></body></html>`;
|
|
3
|
+
const html = `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Supercuts funded-run evidence</title><style>body{font:15px system-ui;margin:0;background:#0b1020;color:#eef2ff}header{position:sticky;top:0;padding:18px 24px;background:#111936;border-bottom:1px solid #334155}main{max-width:980px;margin:auto;padding:24px}.event{display:grid;grid-template-columns:9rem 10rem 1fr;gap:14px;padding:14px;border-bottom:1px solid #27334f}.mode{color:#fbbf24;font-weight:700}.complete,.allowed{color:#86efac}.failed,.rejected{color:#fca5a5}code{color:#bfdbfe;white-space:pre-wrap}#learned{margin:0 0 24px;padding:18px;border:1px solid #fbbf24;border-radius:10px;background:#1a1f3a}#learned h2{margin:0 0 6px;font-size:17px}#learned p{margin:0 0 12px;color:#fde68a;font-weight:600}table{width:100%;border-collapse:collapse;font-size:14px}th,td{text-align:left;padding:6px 8px;border-bottom:1px solid #27334f}th{color:#94a3b8;font-weight:500}.event.memory{background:#1a1f3a}</style></head><body><header><b>Supercuts</b> · <span class="mode" id="mode">loading evidence…</span></header><main><section id="learned" hidden><h2>What Supercuts learned from Walrus Memory</h2><p></p><table><thead><tr><th>Seller</th><th>Capability</th><th>Reliability</th><th>Succeeded / failed</th><th>Last outcome</th><th>Median latency</th></tr></thead><tbody></tbody></table></section><div id="events"></div></main><script>const out=document.querySelector('#events'),learned=document.querySelector('#learned');function renderLearned(d){learned.hidden=false;const w=d.withMemory||{},o=d.withoutMemory;learned.querySelector('p').textContent=d.changedSeller?'Memory changed who gets paid: '+(o?o.sellerId.slice(0,12):'no eligible seller')+' \u2192 '+String(w.sellerId).slice(0,12):(d.changedRail||d.changedRisk||d.changedMaximum)?'Memory changed how this payment settles: '+w.rail+', risk '+w.riskLevel+', max '+w.maximumAmount:'Memory confirmed the default choice';const t=learned.querySelector('tbody');t.textContent='';for(const s of d.scorecards||[]){const r=document.createElement('tr');for(const c of [String(s.counterparty).slice(0,12),s.capabilityId,Math.round(s.reliability*100)+'%',s.successes+' / '+s.failures,s.lastOutcome||'\u2014',s.medianLatencyMs!=null?(s.medianLatencyMs/1000).toFixed(1)+'s':'\u2014']){const td=document.createElement('td');td.textContent=c;r.append(td)}t.append(r)}}const source=new EventSource('/events');source.onmessage=e=>{const v=JSON.parse(e.data);document.querySelector('#mode').textContent=v.mode==='live'?'LIVE FUNDED RUN':'RECORDED FUNDED RUN';if(v.stage==='memory-influence')renderLearned(v.publicDetails);const n=document.createElement('div');n.className=v.stage.startsWith('memory')?'event memory':'event';n.innerHTML='<span>#'+v.sequence+' '+v.actor+'</span><span class="'+v.status+'">'+v.stage+' · '+v.status+'</span><code></code>';n.querySelector('code').textContent=JSON.stringify(v.publicDetails,null,2);out.append(n)};</script></body></html>`;
|
|
4
4
|
export function serveEventDashboard(path, port, replay = false) {
|
|
5
5
|
const server = createServer((request, response) => {
|
|
6
6
|
if (request.url === "/") {
|
package/dist/deal-adapter.js
CHANGED
|
@@ -16,8 +16,6 @@ export async function createConfiguredDealEngine(app) {
|
|
|
16
16
|
fail("MANDATE_COMMAND_FORBIDDEN", "owner mandate does not allow composite deal execution");
|
|
17
17
|
const path = join(app.home, "deal-ports.mjs");
|
|
18
18
|
if (!existsSync(path)) {
|
|
19
|
-
if (!app.store.getSecret("live:memory-config"))
|
|
20
|
-
fail("LIVE_MEMORY_CONFIG_MISSING", "live composite deals require an owner-only Walrus Memory configuration");
|
|
21
19
|
return new DealEngine(new DealJournal(app.store), new LiveDealPorts(app, mandate), mandate);
|
|
22
20
|
}
|
|
23
21
|
if ((statSync(path).mode & 0o077) !== 0)
|
package/dist/deal-engine.d.ts
CHANGED
|
@@ -35,6 +35,16 @@ export interface DealOutcomeQueued {
|
|
|
35
35
|
}
|
|
36
36
|
/** A port owns transport, signing, and finality checks. It must be safe to call
|
|
37
37
|
* again with the same child IDs after a process crash. */
|
|
38
|
+
export interface SellerFault {
|
|
39
|
+
stage: "negotiation" | "delivery";
|
|
40
|
+
code: string;
|
|
41
|
+
paymentDigest?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Codes the buyer runtime verified against seller-supplied evidence. Buyer-side, network, and Walrus readback
|
|
45
|
+
* errors are excluded: a readback mismatch can come from an aggregator, not the seller.
|
|
46
|
+
*/
|
|
47
|
+
export declare const SELLER_FAULT_CODES: Set<string>;
|
|
38
48
|
export interface DealPorts {
|
|
39
49
|
readBlob(blobId: string): Promise<Uint8Array>;
|
|
40
50
|
discover(input: ExecuteDealInput): Promise<SignedCapabilityRegistration>;
|
|
@@ -50,6 +60,8 @@ export interface DealPorts {
|
|
|
50
60
|
unresolved: true;
|
|
51
61
|
}>;
|
|
52
62
|
inventory(blobId: string): Promise<boolean>;
|
|
63
|
+
/** Optional: queue a verified seller-fault memory so later discovery demotes this seller. Best effort. */
|
|
64
|
+
rememberFailure?(input: ExecuteDealInput, plan: SettlementPlanV1, fault: SellerFault): Promise<void>;
|
|
53
65
|
}
|
|
54
66
|
export declare class DealEngine {
|
|
55
67
|
private readonly journal;
|
|
@@ -58,5 +70,6 @@ export declare class DealEngine {
|
|
|
58
70
|
constructor(journal: DealJournal, ports: DealPorts, mandate: AgentMandate);
|
|
59
71
|
execute(input: ExecuteDealInput): Promise<DealView>;
|
|
60
72
|
resume(operationId: string): Promise<DealView>;
|
|
73
|
+
private rememberSellerFault;
|
|
61
74
|
private finishOutcome;
|
|
62
75
|
}
|
package/dist/deal-engine.js
CHANGED
|
@@ -3,6 +3,11 @@ import { validateCapabilityPayload, verifySignedCapability } from "./capability.
|
|
|
3
3
|
import { dealChildId } from "./deal.js";
|
|
4
4
|
import { fail, SupercutsError } from "./errors.js";
|
|
5
5
|
import { canonicalJson, validateSettlementPlan } from "./settlement.js";
|
|
6
|
+
/**
|
|
7
|
+
* Codes the buyer runtime verified against seller-supplied evidence. Buyer-side, network, and Walrus readback
|
|
8
|
+
* errors are excluded: a readback mismatch can come from an aggregator, not the seller.
|
|
9
|
+
*/
|
|
10
|
+
export const SELLER_FAULT_CODES = new Set(["AGREEMENT_BINDING_MISMATCH", "DEAL_RESOURCE_MISMATCH", "DEAL_AGREEMENT_INVALID", "DEAL_DELIVERY_UNVERIFIED", "CAPABILITY_PAYLOAD_INVALID"]);
|
|
6
11
|
const hash = (value) => createHash("sha256").update(canonicalJson(value)).digest("hex");
|
|
7
12
|
export class DealEngine {
|
|
8
13
|
journal;
|
|
@@ -91,20 +96,26 @@ export class DealEngine {
|
|
|
91
96
|
let negotiation = this.journal.step(operationId, "negotiation");
|
|
92
97
|
if (view.state === "planned")
|
|
93
98
|
view = this.journal.advance(operationId, "planned", "negotiating");
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
try {
|
|
100
|
+
if (!negotiation) {
|
|
101
|
+
const started = Date.now();
|
|
102
|
+
negotiation = await this.ports.negotiate(input, registration, plan, dealChildId(operationId, "agreement"));
|
|
103
|
+
if (!negotiation.agreementBlobId || !/^[0-9a-f]{64}$/.test(negotiation.agreementCiphertextHash) || !Array.isArray(negotiation.transcriptBlobs) || !negotiation.transcriptBlobs.length)
|
|
104
|
+
fail("DEAL_AGREEMENT_INVALID", "seller agreement or Walrus transcript evidence is incomplete");
|
|
105
|
+
this.journal.saveStep(operationId, "negotiation", { planHash: plan.planHash }, negotiation);
|
|
106
|
+
this.journal.recordTiming(operationId, "negotiation", Date.now() - started);
|
|
107
|
+
}
|
|
108
|
+
const agreementReadStarted = Date.now();
|
|
109
|
+
for (const item of [...negotiation.transcriptBlobs, { blobId: negotiation.agreementBlobId, ciphertextHash: negotiation.agreementCiphertextHash }]) {
|
|
110
|
+
if (!item.blobId || !/^[0-9a-f]{64}$/.test(item.ciphertextHash) || createHash("sha256").update(await this.ports.readBlob(item.blobId)).digest("hex") !== item.ciphertextHash)
|
|
111
|
+
fail("WALRUS_BLOB_MISMATCH", "encrypted negotiation or agreement differs from Walrus readback");
|
|
112
|
+
}
|
|
113
|
+
this.journal.recordTiming(operationId, "agreement_readback", Date.now() - agreementReadStarted);
|
|
101
114
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
fail("WALRUS_BLOB_MISMATCH", "encrypted negotiation or agreement differs from Walrus readback");
|
|
115
|
+
catch (error) {
|
|
116
|
+
await this.rememberSellerFault(input, plan, "negotiation", undefined, error);
|
|
117
|
+
throw error;
|
|
106
118
|
}
|
|
107
|
-
this.journal.recordTiming(operationId, "agreement_readback", Date.now() - agreementReadStarted);
|
|
108
119
|
if (view.state === "negotiating")
|
|
109
120
|
view = this.journal.advance(operationId, "negotiating", "agreed");
|
|
110
121
|
this.journal.recordBlob(operationId, "agreement", negotiation.agreementCiphertextHash, negotiation.agreementBlobId);
|
|
@@ -139,29 +150,35 @@ export class DealEngine {
|
|
|
139
150
|
let artifact = this.journal.step(operationId, "delivery");
|
|
140
151
|
if (view.state === "paid")
|
|
141
152
|
view = this.journal.advance(operationId, "paid", "delivery_pending");
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
try {
|
|
154
|
+
if (!artifact) {
|
|
155
|
+
const started = Date.now();
|
|
156
|
+
const candidate = await this.ports.deliver(input, plan, negotiation, payment, dealChildId(operationId, "delivery"));
|
|
157
|
+
if ("pending" in candidate)
|
|
158
|
+
return this.journal.get(operationId);
|
|
159
|
+
if (!candidate.blobId || !/^[0-9a-f]{64}$/.test(candidate.ciphertextHash) || !/^[0-9a-f]{64}$/.test(candidate.artifactSha256))
|
|
160
|
+
fail("DEAL_DELIVERY_UNVERIFIED", "seller delivery evidence is incomplete");
|
|
161
|
+
if (typeof candidate.artifactBase64 !== "string" || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(candidate.artifactBase64))
|
|
162
|
+
fail("DEAL_DELIVERY_UNVERIFIED", "seller artifact encoding is invalid");
|
|
163
|
+
const artifactBytes = Buffer.from(candidate.artifactBase64, "base64");
|
|
164
|
+
if (!artifactBytes.length || artifactBytes.length > 512 * 1024 || createHash("sha256").update(artifactBytes).digest("hex") !== candidate.artifactSha256)
|
|
165
|
+
fail("DEAL_DELIVERY_UNVERIFIED", "seller artifact hash or size is invalid");
|
|
166
|
+
validateCapabilityPayload(registration.manifest, "output", candidate.metadata);
|
|
167
|
+
artifact = candidate;
|
|
168
|
+
this.journal.saveStep(operationId, "delivery", { paymentDigest: payment.transactionDigest }, artifact);
|
|
169
|
+
this.journal.recordTiming(operationId, "delivery", Date.now() - started);
|
|
170
|
+
}
|
|
171
|
+
if (createHash("sha256").update(Buffer.from(artifact.artifactBase64, "base64")).digest("hex") !== artifact.artifactSha256)
|
|
172
|
+
fail("DEAL_DELIVERY_UNVERIFIED", "saved artifact hash differs from delivery evidence");
|
|
173
|
+
const deliveryReadStarted = Date.now();
|
|
174
|
+
if (createHash("sha256").update(await this.ports.readBlob(artifact.blobId)).digest("hex") !== artifact.ciphertextHash)
|
|
175
|
+
fail("WALRUS_BLOB_MISMATCH", "encrypted delivery differs from Walrus readback");
|
|
176
|
+
this.journal.recordTiming(operationId, "delivery_readback", Date.now() - deliveryReadStarted);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
await this.rememberSellerFault(input, plan, "delivery", payment.transactionDigest, error);
|
|
180
|
+
throw error;
|
|
158
181
|
}
|
|
159
|
-
if (createHash("sha256").update(Buffer.from(artifact.artifactBase64, "base64")).digest("hex") !== artifact.artifactSha256)
|
|
160
|
-
fail("DEAL_DELIVERY_UNVERIFIED", "saved artifact hash differs from delivery evidence");
|
|
161
|
-
const deliveryReadStarted = Date.now();
|
|
162
|
-
if (createHash("sha256").update(await this.ports.readBlob(artifact.blobId)).digest("hex") !== artifact.ciphertextHash)
|
|
163
|
-
fail("WALRUS_BLOB_MISMATCH", "encrypted delivery differs from Walrus readback");
|
|
164
|
-
this.journal.recordTiming(operationId, "delivery_readback", Date.now() - deliveryReadStarted);
|
|
165
182
|
this.journal.recordBlob(operationId, "delivery", artifact.ciphertextHash, artifact.blobId);
|
|
166
183
|
if (view.state === "delivery_pending")
|
|
167
184
|
view = this.journal.advance(operationId, "delivery_pending", "delivered");
|
|
@@ -169,6 +186,14 @@ export class DealEngine {
|
|
|
169
186
|
view = this.journal.advance(operationId, "delivered", "outcome_pending");
|
|
170
187
|
return this.finishOutcome(operationId, input);
|
|
171
188
|
}
|
|
189
|
+
async rememberSellerFault(input, plan, stage, paymentDigest, error) {
|
|
190
|
+
if (!(error instanceof SupercutsError) || !SELLER_FAULT_CODES.has(error.code) || !this.ports.rememberFailure)
|
|
191
|
+
return;
|
|
192
|
+
try {
|
|
193
|
+
await this.ports.rememberFailure(input, plan, { stage, code: error.code, ...(paymentDigest ? { paymentDigest } : {}) });
|
|
194
|
+
}
|
|
195
|
+
catch { /* the original seller fault stays the reported error */ }
|
|
196
|
+
}
|
|
172
197
|
async finishOutcome(operationId, input) {
|
|
173
198
|
const view = this.journal.get(operationId);
|
|
174
199
|
if (view.evidence.outcomeMemory === "indexed")
|
|
@@ -179,10 +204,10 @@ export class DealEngine {
|
|
|
179
204
|
if (existing?.blobId) {
|
|
180
205
|
const indexed = await this.ports.inventory(existing.blobId);
|
|
181
206
|
this.journal.recordOutcome(operationId, existing.payloadHash, existing.blobId, indexed);
|
|
182
|
-
return
|
|
207
|
+
return this.journal.advance(operationId, "outcome_pending", "complete");
|
|
183
208
|
}
|
|
184
209
|
if (existing)
|
|
185
|
-
return
|
|
210
|
+
return this.journal.advance(operationId, "outcome_pending", "complete");
|
|
186
211
|
const plan = this.journal.step(operationId, "planning") ?? fail("DEAL_PLAN_MISSING", "saved deal plan is missing");
|
|
187
212
|
const payment = this.journal.step(operationId, "payment") ?? fail("DEAL_PAYMENT_UNVERIFIED", "saved deal payment is missing");
|
|
188
213
|
const artifact = this.journal.step(operationId, "delivery") ?? fail("DEAL_DELIVERY_UNVERIFIED", "saved deal delivery is missing");
|
|
@@ -193,11 +218,11 @@ export class DealEngine {
|
|
|
193
218
|
this.journal.recordTiming(operationId, "outcome_queue", Date.now() - started);
|
|
194
219
|
if ("unresolved" in candidate) {
|
|
195
220
|
this.journal.setOutcomeUnresolved(operationId);
|
|
196
|
-
return this.journal.
|
|
221
|
+
return this.journal.advance(operationId, "outcome_pending", "complete");
|
|
197
222
|
}
|
|
198
223
|
if ("queued" in candidate) {
|
|
199
224
|
this.journal.queueOutcome(operationId, candidate.payloadHash);
|
|
200
|
-
return this.journal.
|
|
225
|
+
return this.journal.advance(operationId, "outcome_pending", "complete");
|
|
201
226
|
}
|
|
202
227
|
if (!candidate.blobId || !/^[0-9a-f]{64}$/.test(candidate.payloadHash))
|
|
203
228
|
fail("DEAL_MEMORY_INVALID", "outcome memory is incomplete");
|
|
@@ -206,6 +231,6 @@ export class DealEngine {
|
|
|
206
231
|
}
|
|
207
232
|
const indexed = await this.ports.inventory(outcome.blobId);
|
|
208
233
|
this.journal.recordOutcome(operationId, outcome.payloadHash, outcome.blobId, indexed);
|
|
209
|
-
return
|
|
234
|
+
return this.journal.advance(operationId, "outcome_pending", "complete");
|
|
210
235
|
}
|
|
211
236
|
}
|
package/dist/deal.d.ts
CHANGED
|
@@ -43,6 +43,41 @@ export interface DealEvidenceView {
|
|
|
43
43
|
inventory: "pending" | "indexed" | "missing" | "unresolved";
|
|
44
44
|
paymentFinalized: boolean;
|
|
45
45
|
paymentDigest?: string;
|
|
46
|
+
/** What Walrus Memory changed about this payment versus the same mandate with no memory. */
|
|
47
|
+
memoryInfluence?: {
|
|
48
|
+
changedSeller: boolean;
|
|
49
|
+
changedRail: boolean;
|
|
50
|
+
changedRisk: boolean;
|
|
51
|
+
changedMaximum: boolean;
|
|
52
|
+
withoutMemory?: {
|
|
53
|
+
sellerId: string;
|
|
54
|
+
capabilityId: string;
|
|
55
|
+
rail: string;
|
|
56
|
+
riskLevel: string;
|
|
57
|
+
maximumAmount: string;
|
|
58
|
+
};
|
|
59
|
+
withMemory: {
|
|
60
|
+
sellerId: string;
|
|
61
|
+
capabilityId: string;
|
|
62
|
+
rail: string;
|
|
63
|
+
riskLevel: string;
|
|
64
|
+
maximumAmount: string;
|
|
65
|
+
};
|
|
66
|
+
citedBlobIds: string[];
|
|
67
|
+
};
|
|
68
|
+
/** Learned seller scorecards used for discovery; counts and blob IDs only, never memory plaintext. */
|
|
69
|
+
memoryRanking?: Array<{
|
|
70
|
+
capabilityId: string;
|
|
71
|
+
counterparty: string;
|
|
72
|
+
reliability: number;
|
|
73
|
+
observations: number;
|
|
74
|
+
successes: number;
|
|
75
|
+
failures: number;
|
|
76
|
+
unresolved: number;
|
|
77
|
+
lastOutcome?: string;
|
|
78
|
+
medianLatencyMs?: number;
|
|
79
|
+
priceMinimum: string;
|
|
80
|
+
}>;
|
|
46
81
|
}
|
|
47
82
|
export interface DealDelivery {
|
|
48
83
|
dealId: string;
|
package/dist/deal.js
CHANGED
|
@@ -176,7 +176,13 @@ export class DealJournal {
|
|
|
176
176
|
const blobs = this.store.db.prepare("SELECT kind,blob_id,status FROM deal_blobs WHERE operation_id=?").all(row.operation_id);
|
|
177
177
|
const memory = this.store.db.prepare("SELECT blob_id,status FROM deal_memory_outbox WHERE operation_id=? AND event_kind='outcome'").get(row.operation_id);
|
|
178
178
|
const transcript = this.step(row.operation_id, "negotiation");
|
|
179
|
-
|
|
179
|
+
const hasRecords = Boolean(this.store.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='app_records'").get());
|
|
180
|
+
const record = (scope, id) => { if (!hasRecords)
|
|
181
|
+
return undefined; const found = this.store.db.prepare("SELECT value FROM app_records WHERE scope=? AND id=?").get(scope, id); return found ? JSON.parse(found.value) : undefined; };
|
|
182
|
+
const influence = record("memory-influence", `deal:${row.operation_id}`);
|
|
183
|
+
const ranking = record("deal-ranking", row.operation_id);
|
|
184
|
+
const memoryRanking = ranking?.scorecards.map(({ capabilityId, counterparty, reliability, observations, successes, failures, unresolved, lastOutcome, medianLatencyMs, priceMinimum }) => ({ capabilityId, counterparty, reliability, observations, successes, failures, unresolved, ...(lastOutcome ? { lastOutcome } : {}), ...(medianLatencyMs !== undefined ? { medianLatencyMs } : {}), priceMinimum }));
|
|
185
|
+
return { version: "supercuts.deal-evidence.v1", dealId, transcriptBlobIds: transcript?.transcriptBlobs?.map((item) => item.blobId) ?? [], ...(blobs.find((item) => item.kind === "agreement")?.blob_id ? { agreementBlobId: blobs.find((item) => item.kind === "agreement").blob_id } : {}), ...(blobs.find((item) => item.kind === "delivery")?.blob_id ? { deliveryBlobId: blobs.find((item) => item.kind === "delivery").blob_id } : {}), ...(memory?.blob_id ? { outcomeMemoryBlobId: memory.blob_id } : {}), ciphertextReadback: blobs.some((item) => item.status === "unresolved") ? "unresolved" : ["agreement", "delivery"].every((kind) => blobs.some((item) => item.kind === kind && item.status === "verified")) ? "verified" : "pending", inventory: memory?.status === "indexed" ? "indexed" : memory?.status === "unresolved" ? "unresolved" : "pending", paymentFinalized: view.evidence.payment === "finalized", ...(view.paymentDigest ? { paymentDigest: view.paymentDigest } : {}), ...(influence ? { memoryInfluence: influence } : {}), ...(memoryRanking ? { memoryRanking } : {}) };
|
|
180
186
|
}
|
|
181
187
|
delivery(dealId) {
|
|
182
188
|
const row = this.store.db.prepare("SELECT operation_id FROM deal_operations WHERE deal_id=?").get(dealId);
|
|
@@ -213,8 +219,8 @@ export class DealJournal {
|
|
|
213
219
|
fail("DEAL_PAYMENT_UNVERIFIED", "confirmed payment evidence is required");
|
|
214
220
|
if (next === "delivered" && !(this.store.db.prepare("SELECT 1 FROM deal_blobs WHERE operation_id=? AND kind='delivery' AND status='verified'").get(operationId)))
|
|
215
221
|
fail("DEAL_DELIVERY_UNVERIFIED", "verified delivery evidence is required");
|
|
216
|
-
if (next === "complete" &&
|
|
217
|
-
fail("
|
|
222
|
+
if (next === "complete" && (this.get(operationId).evidence.payment !== "finalized" || this.get(operationId).evidence.delivery !== "verified"))
|
|
223
|
+
fail("DEAL_DELIVERY_UNVERIFIED", "finalized payment and verified delivery are required before deal completion");
|
|
218
224
|
this.store.db.prepare("UPDATE deal_operations SET state=?,rail=COALESCE(?,rail),plan_hash=COALESCE(?,plan_hash),updated_at=? WHERE operation_id=?")
|
|
219
225
|
.run(next, result?.rail ?? null, result?.planHash ?? null, Date.now(), operationId);
|
|
220
226
|
this.store.db.exec("COMMIT");
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* OpenAI-compatible embedding adapter for local models (`supercuts-embedding-adapter`).
|
|
4
|
+
*
|
|
5
|
+
* Walrus Mainnet Memory indexes 1536-dimensional embeddings. This loopback proxy forwards requests to a
|
|
6
|
+
* local OpenAI-compatible endpoint (Ollama by default) and zero-pads shorter vectors to 1536 dimensions.
|
|
7
|
+
* Zero-padding preserves cosine similarity and Euclidean distance as long as every write and recall uses
|
|
8
|
+
* this same adapter and upstream model. Memory plaintext only reaches the configured upstream.
|
|
9
|
+
*/
|
|
10
|
+
import { type Server } from "node:http";
|
|
11
|
+
export declare const TARGET_DIMENSIONS = 1536;
|
|
12
|
+
export declare function padEmbedding(embedding: unknown): number[];
|
|
13
|
+
interface EmbeddingPayload {
|
|
14
|
+
data: Array<{
|
|
15
|
+
embedding: unknown;
|
|
16
|
+
} & Record<string, unknown>>;
|
|
17
|
+
}
|
|
18
|
+
export declare function transformEmbeddingResponse(payload: unknown): EmbeddingPayload;
|
|
19
|
+
export interface EmbeddingAdapterOptions {
|
|
20
|
+
upstreamBaseUrl: string;
|
|
21
|
+
upstreamModel: string;
|
|
22
|
+
upstreamApiKey?: string;
|
|
23
|
+
fetchImpl?: typeof fetch;
|
|
24
|
+
}
|
|
25
|
+
export declare function createEmbeddingAdapter({ fetchImpl, upstreamBaseUrl, upstreamApiKey, upstreamModel, }: EmbeddingAdapterOptions): Server;
|
|
26
|
+
export {};
|