run402 4.17.0 → 4.17.2

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.
@@ -0,0 +1,93 @@
1
+ {
2
+ "fixture_version": 1,
3
+ "fixture_id": "buzz-cli-v0.5.2-capabilities",
4
+ "purpose": "released_buzz_cli_capability_first_compatibility",
5
+ "buzz_release": {
6
+ "version": "0.5.2",
7
+ "release_tag": "v0.5.2",
8
+ "release_tag_commit": "3e48f1b2365d326ee1c9582448d86a99b44ecd5d",
9
+ "run402_change_required": false
10
+ },
11
+ "help_probes": [
12
+ {
13
+ "argv": ["buzz", "--help"],
14
+ "exit_code": 0,
15
+ "stdout_contains": [
16
+ "Buzz CLI — interact with a Buzz relay",
17
+ "users",
18
+ "social",
19
+ "Errors are JSON on stderr"
20
+ ]
21
+ },
22
+ {
23
+ "argv": ["buzz", "users", "get", "--help"],
24
+ "exit_code": 0,
25
+ "stdout_contains": [
26
+ "Usage: buzz users get",
27
+ "--pubkey",
28
+ "Omit for your own profile"
29
+ ]
30
+ },
31
+ {
32
+ "argv": ["buzz", "social", "publish", "--help"],
33
+ "exit_code": 0,
34
+ "stdout_contains": [
35
+ "Usage: buzz social publish",
36
+ "--content"
37
+ ]
38
+ },
39
+ {
40
+ "argv": ["buzz", "social", "event", "--help"],
41
+ "exit_code": 0,
42
+ "stdout_contains": [
43
+ "Usage: buzz social event",
44
+ "--event"
45
+ ]
46
+ }
47
+ ],
48
+ "version_probe": {
49
+ "argv": ["buzz", "--version"],
50
+ "supported": false,
51
+ "exit_code": 1,
52
+ "stderr_json": {
53
+ "error": "user_error",
54
+ "retryable": false
55
+ },
56
+ "message_contains": "unexpected argument '--version'"
57
+ },
58
+ "json_contract": {
59
+ "default_output_format": "json",
60
+ "users_get_success_stdout": {
61
+ "type": "array",
62
+ "empty_array_means": "public_self_profile_unverified",
63
+ "profile_pubkey_field": "pubkey"
64
+ },
65
+ "social_event_success_stdout": {
66
+ "type": "array",
67
+ "event_pubkey_field": "pubkey"
68
+ },
69
+ "error_stderr": {
70
+ "type": "object",
71
+ "required_fields": ["error", "message", "retryable"],
72
+ "stdout_is_empty": true
73
+ }
74
+ },
75
+ "managed_sidecar": {
76
+ "distribution": "bundled_with_buzz_desktop",
77
+ "session_discovery": "managed_agent_path",
78
+ "public_package_manager_install": false,
79
+ "repair_surface": "buzz_settings",
80
+ "repair_destination": "Buzz Desktop > Settings > Updates",
81
+ "repair_instruction": "Open Buzz Desktop > Settings > Updates, install the available Buzz update or reinstall Buzz Desktop if no update is offered, then restart this agent and rerun setup."
82
+ },
83
+ "public_self_observation": {
84
+ "argv": ["buzz", "users", "get"],
85
+ "requires_private_key_value_inspection": false,
86
+ "empty_or_malformed_result": "BUZZ_AGENT_TARGET_UNVERIFIED"
87
+ },
88
+ "write_surface_policy": {
89
+ "capability_probes_use_help_only": true,
90
+ "publish_is_never_invoked_by_doctor": true,
91
+ "event_read_requires_explicit_event_id_and_is_not_used_for_self_discovery": true
92
+ }
93
+ }
@@ -0,0 +1,140 @@
1
+ {
2
+ "schema_version": 1,
3
+ "contract_id": "run402.buzz-doctor.v1",
4
+ "mode": "buzz",
5
+ "canonical_invocation": [
6
+ "run402",
7
+ "--wallet",
8
+ "<profile>",
9
+ "doctor",
10
+ "--buzz",
11
+ "--buzz-agent",
12
+ "<npub-or-hex>"
13
+ ],
14
+ "flags": {
15
+ "mode": "--buzz",
16
+ "agent": "--buzz-agent",
17
+ "profile": "--wallet"
18
+ },
19
+ "check_order": [
20
+ "session_shell",
21
+ "node_runtime",
22
+ "run402_cli",
23
+ "buzz_cli",
24
+ "buzz_agent_target",
25
+ "run402_api",
26
+ "run402_console",
27
+ "buzz_relay",
28
+ "wallet_profile"
29
+ ],
30
+ "statuses": [
31
+ "ok",
32
+ "warning",
33
+ "blocked"
34
+ ],
35
+ "codes_by_check": {
36
+ "session_shell": [
37
+ "BUZZ_PREFLIGHT_SHELL_UNAVAILABLE"
38
+ ],
39
+ "node_runtime": [
40
+ "BUZZ_PREFLIGHT_NODE_UNAVAILABLE",
41
+ "BUZZ_PREFLIGHT_NODE_INCOMPATIBLE"
42
+ ],
43
+ "run402_cli": [
44
+ "BUZZ_PREFLIGHT_RUN402_UNAVAILABLE",
45
+ "BUZZ_PREFLIGHT_RUN402_INCOMPATIBLE",
46
+ "BUZZ_PREFLIGHT_RUN402_UPDATE_AVAILABLE"
47
+ ],
48
+ "buzz_cli": [
49
+ "BUZZ_PREFLIGHT_BUZZ_CLI_UNAVAILABLE",
50
+ "BUZZ_PREFLIGHT_BUZZ_CLI_INCOMPATIBLE"
51
+ ],
52
+ "buzz_agent_target": [
53
+ "BUZZ_AGENT_TARGET_REQUIRED",
54
+ "BUZZ_AGENT_TARGET_UNVERIFIED",
55
+ "BUZZ_AGENT_TARGET_MISMATCH"
56
+ ],
57
+ "run402_api": [
58
+ "BUZZ_PREFLIGHT_API_UNREACHABLE"
59
+ ],
60
+ "run402_console": [
61
+ "BUZZ_PREFLIGHT_CONSOLE_UNREACHABLE"
62
+ ],
63
+ "buzz_relay": [
64
+ "BUZZ_PREFLIGHT_RELAY_UNSAFE",
65
+ "BUZZ_PREFLIGHT_RELAY_UNREACHABLE"
66
+ ],
67
+ "wallet_profile": [
68
+ "BUZZ_PREFLIGHT_WALLET_PROFILE_REQUIRED",
69
+ "BUZZ_PREFLIGHT_WALLET_PROFILE_NOT_FOUND",
70
+ "BUZZ_PREFLIGHT_WALLET_PROFILE_MISMATCH"
71
+ ]
72
+ },
73
+ "next_action": {
74
+ "required_fields": [
75
+ "type",
76
+ "surface",
77
+ "command",
78
+ "why",
79
+ "safe_to_auto_execute",
80
+ "requires_approval",
81
+ "destructive",
82
+ "idempotent",
83
+ "spend_impact"
84
+ ],
85
+ "surfaces": [
86
+ "shell",
87
+ "buzz_chat",
88
+ "buzz_settings"
89
+ ],
90
+ "shell_required_fields": [
91
+ "argv"
92
+ ],
93
+ "non_shell_forbidden_fields": [
94
+ "argv",
95
+ "cwd"
96
+ ],
97
+ "cardinality_per_actionable_check": 1
98
+ },
99
+ "exit_behavior": {
100
+ "passed_or_warning_only": 0,
101
+ "completed_with_blocked_checks": 1,
102
+ "usage_error": 1,
103
+ "completed_report_stream": "stdout",
104
+ "usage_error_stream": "stderr"
105
+ },
106
+ "freshness": {
107
+ "max_age_seconds": 60,
108
+ "binding_fields": [
109
+ "contract_id",
110
+ "expected_subject_hex",
111
+ "wallet_profile",
112
+ "node_executable",
113
+ "run402_executable",
114
+ "relay_origin"
115
+ ]
116
+ },
117
+ "setup_rejection_codes": [
118
+ "BUZZ_PREFLIGHT_REPORT_INVALID",
119
+ "BUZZ_PREFLIGHT_REPORT_STALE",
120
+ "BUZZ_PREFLIGHT_REPORT_MISMATCH"
121
+ ],
122
+ "zero_mutation": {
123
+ "mutation_state": "not_started",
124
+ "forbidden": [
125
+ "buzz_event_publish",
126
+ "buzz_arbitrary_sign",
127
+ "run402_profile_create_or_select",
128
+ "run402_wallet_create",
129
+ "run402_identity_link_mutation",
130
+ "run402_faucet",
131
+ "run402_tier_mutation",
132
+ "run402_project_mutation",
133
+ "run402_deploy"
134
+ ],
135
+ "allowed_lossy_local_writes": [
136
+ "cli_update_cache",
137
+ "redacted_diagnostic_queue"
138
+ ]
139
+ }
140
+ }
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
 
3
- const CONTRACT_URL = new URL("../../buzz/fixtures/run402-buzz-doctor-v1-contract.json", import.meta.url);
3
+ const CONTRACT_URL = new URL("../fixtures/run402-buzz-doctor-v1-contract.json", import.meta.url);
4
4
 
5
5
  export const BUZZ_DOCTOR_CONTRACT = deepFreeze(JSON.parse(readFileSync(CONTRACT_URL, "utf8")));
6
6
  export const BUZZ_DOCTOR_CONTRACT_ID = BUZZ_DOCTOR_CONTRACT.contract_id;
@@ -9,7 +9,9 @@ import {
9
9
  validateBuzzDoctorReport,
10
10
  } from "./buzz-doctor-contract.mjs";
11
11
 
12
- const FIXTURE = JSON.parse(readFileSync(new URL("../../buzz/fixtures/buzz-v0.5.2-cli-capabilities.json", import.meta.url), "utf8"));
12
+ const FIXTURE = JSON.parse(readFileSync(new URL("../fixtures/buzz-v0.5.2-cli-capabilities.json", import.meta.url), "utf8"));
13
+ const RELEASED_FIXTURE = JSON.parse(readFileSync(new URL("../../buzz/fixtures/buzz-v0.5.2-cli-capabilities.json", import.meta.url), "utf8"));
14
+ const RELEASED_CONTRACT = JSON.parse(readFileSync(new URL("../../buzz/fixtures/run402-buzz-doctor-v1-contract.json", import.meta.url), "utf8"));
13
15
 
14
16
  function shellAction() {
15
17
  return {
@@ -47,6 +49,11 @@ function passingReport(now) {
47
49
  }
48
50
 
49
51
  describe("Buzz doctor v1 frozen contract", () => {
52
+ it("packages byte-semantic copies of the released Buzz fixture and frozen doctor contract", () => {
53
+ assert.deepEqual(FIXTURE, RELEASED_FIXTURE);
54
+ assert.deepEqual(BUZZ_DOCTOR_CONTRACT, RELEASED_CONTRACT);
55
+ });
56
+
50
57
  it("freezes the ordered checks, flags, statuses, exit streams, freshness binding, and zero-mutation verdict", () => {
51
58
  assert.equal(BUZZ_DOCTOR_CONTRACT_ID, "run402.buzz-doctor.v1");
52
59
  assert.deepEqual(BUZZ_DOCTOR_CHECK_ORDER, [
@@ -15,7 +15,7 @@ import {
15
15
  } from "./buzz-doctor-contract.mjs";
16
16
 
17
17
  const BUZZ_CAPABILITIES = JSON.parse(readFileSync(
18
- new URL("../../buzz/fixtures/buzz-v0.5.2-cli-capabilities.json", import.meta.url),
18
+ new URL("../fixtures/buzz-v0.5.2-cli-capabilities.json", import.meta.url),
19
19
  "utf8",
20
20
  ));
21
21
 
@@ -42,7 +42,10 @@ export const BUZZ_DOCTOR_REPAIR_MATRIX = Object.freeze({
42
42
  BUZZ_PREFLIGHT_API_UNREACHABLE: { type: "restore_run402_api_access", surface: "buzz_settings" },
43
43
  BUZZ_PREFLIGHT_CONSOLE_UNREACHABLE: { type: "restore_run402_console_access", surface: "buzz_settings" },
44
44
  BUZZ_PREFLIGHT_RELAY_UNSAFE: { type: "repair_buzz_relay_url", surface: "buzz_settings" },
45
- BUZZ_PREFLIGHT_RELAY_UNREACHABLE: { type: "restore_buzz_relay_access", surface: "buzz_settings" },
45
+ BUZZ_PREFLIGHT_RELAY_UNREACHABLE: {
46
+ type: ["repair_buzz_relay_tls", "repair_buzz_relay_dns", "repair_buzz_relay_service", "restore_buzz_relay_access"],
47
+ surface: "buzz_settings",
48
+ },
46
49
  BUZZ_PREFLIGHT_WALLET_PROFILE_REQUIRED: { type: "rerun_buzz_setup_with_explicit_identity", surface: "buzz_chat" },
47
50
  BUZZ_PREFLIGHT_WALLET_PROFILE_NOT_FOUND: { type: "create_buzz_wallet_profile", surface: "shell" },
48
51
  BUZZ_PREFLIGHT_WALLET_PROFILE_MISMATCH: { type: "rerun_buzz_doctor", surface: "shell" },
@@ -343,9 +346,11 @@ async function checkBuzzRelay({ relay, lookup, pinnedRelayRead }) {
343
346
  failure: error.message,
344
347
  });
345
348
  }
346
- return blocked("buzz_relay", "BUZZ_PREFLIGHT_RELAY_UNREACHABLE", "The configured safe Buzz relay did not complete its bounded public read.", relayReachabilityRepair(relay.origin), {
349
+ const failure = safeFailure(error);
350
+ return warning("buzz_relay", "BUZZ_PREFLIGHT_RELAY_UNREACHABLE", "The configured Buzz relay is safe to probe but unavailable; founder-agent setup may continue without community installation or enrollment.", relayAvailabilityRepair(relay.origin, failure), {
347
351
  origin: relay.origin,
348
- failure: safeFailure(error),
352
+ failure,
353
+ community_operations: "unavailable",
349
354
  });
350
355
  }
351
356
  }
@@ -686,12 +691,36 @@ function relayUnsafeRepair() {
686
691
  });
687
692
  }
688
693
 
689
- function relayReachabilityRepair(origin) {
694
+ function relayAvailabilityRepair(origin, failure) {
695
+ if (failure === "tls_handshake_failed") {
696
+ return nonShellAction({
697
+ type: "repair_buzz_relay_tls",
698
+ surface: "buzz_settings",
699
+ command: `Ask the Buzz community operator to provision a valid TLS certificate and public hostname route for ${origin}; after the endpoint completes verified TLS, restart this agent and rerun Run402 setup.`,
700
+ why: "Reconnect cannot repair a relay hostname whose public TLS handshake fails; community installation and enrollment stay unavailable until the endpoint is fixed.",
701
+ });
702
+ }
703
+ if (failure === "dns") {
704
+ return nonShellAction({
705
+ type: "repair_buzz_relay_dns",
706
+ surface: "buzz_settings",
707
+ command: `Ask the Buzz community operator to publish working public DNS for ${origin}; after the hostname resolves publicly, restart this agent and rerun Run402 setup.`,
708
+ why: "Community installation and enrollment require a publicly resolvable relay, while founder-agent setup remains independent.",
709
+ });
710
+ }
711
+ if (failure.startsWith("http_") || failure === "nip11_invalid" || failure === "redirect_rejected" || failure === "response_too_large") {
712
+ return nonShellAction({
713
+ type: "repair_buzz_relay_service",
714
+ surface: "buzz_settings",
715
+ command: `Ask the Buzz community operator to restore the public NIP-11 endpoint at ${origin}; after it returns a bounded non-redirecting JSON response, restart this agent and rerun Run402 setup.`,
716
+ why: "Community installation and enrollment require the relay's public NIP-11 service, while founder-agent setup remains independent.",
717
+ });
718
+ }
690
719
  return nonShellAction({
691
720
  type: "restore_buzz_relay_access",
692
721
  surface: "buzz_settings",
693
- command: `Open Buzz Desktop > Settings > Communities, reconnect the current community relay ${origin}, restart this agent, then rerun Run402 setup.`,
694
- why: "The current safe relay must complete a bounded public NIP-11 read before setup can proceed.",
722
+ command: `Ask the Buzz community operator to restore public relay access at ${origin}; after the endpoint is reachable, restart this agent and rerun Run402 setup.`,
723
+ why: "Community installation and enrollment require a live public relay, while founder-agent setup remains independent.",
695
724
  });
696
725
  }
697
726
 
@@ -777,11 +806,13 @@ function safeOrigin(value) {
777
806
 
778
807
  function safeFailure(error) {
779
808
  if (error?.name === "AbortError") return "timeout";
780
- const message = error instanceof Error ? error.message : String(error);
781
- if (/certificate|tls|ssl/i.test(message)) return "tls";
782
- if (/dns|getaddrinfo|enotfound/i.test(message)) return "dns";
809
+ const message = error instanceof Error ? `${error.message} ${error.code ?? ""} ${error.cause?.message ?? ""} ${error.cause?.code ?? ""}` : String(error);
810
+ if (/certificate|tls|ssl|eproto|secure tls connection|handshake/i.test(message)) return "tls_handshake_failed";
811
+ if (/dns|dns_empty|getaddrinfo|enotfound|eai_again/i.test(message)) return "dns";
783
812
  if (/timeout|timed out|abort/i.test(message)) return "timeout";
784
813
  if (/redirect/i.test(message)) return "redirect_rejected";
814
+ if (/response_too_large/i.test(message)) return "response_too_large";
815
+ if (/nip11_invalid/i.test(message)) return "nip11_invalid";
785
816
  if (/http_\d+/.test(message)) return message.match(/http_\d+/)[0];
786
817
  return "network";
787
818
  }
@@ -194,6 +194,46 @@ describe("Buzz doctor bounded zero-mutation runner", () => {
194
194
  assert.equal(report.ok, true);
195
195
  });
196
196
 
197
+ it("keeps founder setup usable when a public relay hostname fails TLS and returns an operator repair", async () => {
198
+ const tlsError = Object.assign(new Error("write EPROTO ssl3 alert handshake failure"), { code: "EPROTO" });
199
+ const dependencies = healthyDependencies({
200
+ env: {
201
+ ...healthyDependencies().env,
202
+ BUZZ_RELAY_URL: "wss://kychee.com.communities.buzz.xyz",
203
+ },
204
+ pinnedRelayRead: async () => { throw tlsError; },
205
+ });
206
+ const report = await buildBuzzDoctorReport(dependencies);
207
+ const relay = report.checks.find((check) => check.name === "buzz_relay");
208
+ assert.equal(report.ok, true);
209
+ assert.equal(relay.status, "warning");
210
+ assert.equal(relay.code, "BUZZ_PREFLIGHT_RELAY_UNREACHABLE");
211
+ assert.equal(relay.value.failure, "tls_handshake_failed");
212
+ assert.equal(relay.value.community_operations, "unavailable");
213
+ assert.equal(relay.next_actions[0].type, "repair_buzz_relay_tls");
214
+ assert.match(relay.next_actions[0].command, /kychee\.com\.communities\.buzz\.xyz/);
215
+ assert.doesNotMatch(relay.next_actions[0].command, /reconnect/i);
216
+ assert.equal(relay.next_actions[0].surface, "buzz_settings");
217
+ assert.equal("argv" in relay.next_actions[0], false);
218
+ });
219
+
220
+ it("continues to block an unsafe relay destination before making the pinned read", async () => {
221
+ let reads = 0;
222
+ const dependencies = healthyDependencies({
223
+ lookup: async () => [
224
+ { address: "93.184.216.34", family: 4 },
225
+ { address: "127.0.0.1", family: 4 },
226
+ ],
227
+ pinnedRelayRead: async () => { reads += 1; return { ok: true }; },
228
+ });
229
+ const report = await buildBuzzDoctorReport(dependencies);
230
+ const relay = report.checks.find((check) => check.name === "buzz_relay");
231
+ assert.equal(report.ok, false);
232
+ assert.equal(relay.status, "blocked");
233
+ assert.equal(relay.code, "BUZZ_PREFLIGHT_RELAY_UNSAFE");
234
+ assert.equal(reads, 0);
235
+ });
236
+
197
237
  it("rejects private, loopback, link-local, mapped, and documentation addresses", () => {
198
238
  for (const address of ["127.0.0.1", "10.0.0.1", "169.254.169.254", "192.0.2.1", "::1", "fc00::1", "fe80::1", "::ffff:127.0.0.1", "2001:db8::1"]) {
199
239
  assert.equal(isPublicAddress(address), false, address);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "run402",
3
- "version": "4.17.0",
3
+ "version": "4.17.2",
4
4
  "description": "CLI for Run402 — provision Postgres databases, deploy static sites, generate images, and manage wallets via x402 and MPP micropayments.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,6 +15,7 @@
15
15
  "files": [
16
16
  "cli.mjs",
17
17
  "lib/",
18
+ "fixtures/",
18
19
  "core-dist/",
19
20
  "sdk/"
20
21
  ],
@@ -26,6 +27,7 @@
26
27
  "@x402/evm": "^2.19.0",
27
28
  "@x402/extensions": "^2.19.0",
28
29
  "@x402/fetch": "^2.19.0",
30
+ "ipaddr.js": "1.9.1",
29
31
  "json-canonicalize": "2.0.0",
30
32
  "mppx": "^0.4.7",
31
33
  "tsx": "^4.21.0",