@@ -3888,10 +3888,16 @@ describe("CLI e2e happy path", () => {
38883888 }
38893889 assert . ok ( typeof parsed === "object" && parsed !== null , "stdout must be a JSON object" ) ;
38903890 assert . ok ( typeof parsed . config_dir === "string" , "should include config_dir" ) ;
3891- assert . ok ( typeof parsed . allowance === "object" && parsed . allowance ?. address , "should include allowance.address" ) ;
3891+ assert . ok ( typeof parsed . wallet === "object" && parsed . wallet ?. address , "should include wallet.address" ) ;
3892+ assert . ok ( typeof parsed . wallet . local_label === "string" , "wallet should include local_label" ) ;
3893+ assert . ok ( Object . prototype . hasOwnProperty . call ( parsed . wallet , "server_label" ) , "wallet should include server_label" ) ;
3894+ assert . ok ( ! Object . prototype . hasOwnProperty . call ( parsed . wallet , "funded" ) , "wallet should not include stale funded" ) ;
38923895 assert . ok ( parsed . rail === "x402" || parsed . rail === "mpp" , `should include rail, got: ${ parsed . rail } ` ) ;
38933896 assert . ok ( typeof parsed . network === "string" , "should include network" ) ;
3894- assert . ok ( Object . prototype . hasOwnProperty . call ( parsed , "balance" ) , "should include balance field" ) ;
3897+ assert . ok ( typeof parsed . balances === "object" && parsed . balances !== null , "should include balances object" ) ;
3898+ assert . equal ( parsed . balances . on_chain_token , parsed . rail === "mpp" ? "pathUSD" : "USDC" , "on_chain_token should track rail" ) ;
3899+ assert . ok ( ! Object . prototype . hasOwnProperty . call ( parsed , "balance" ) , "should not expose ambiguous balance field" ) ;
3900+ assert . ok ( ! Object . prototype . hasOwnProperty . call ( parsed , "allowance" ) , "should not expose allowance block" ) ;
38953901 assert . ok ( Object . prototype . hasOwnProperty . call ( parsed , "tier" ) , "should include tier field" ) ;
38963902 assert . equal ( typeof parsed . projects_saved , "number" , "should include projects_saved (number)" ) ;
38973903 assert . ok ( typeof parsed . next_step === "string" , "should include next_step" ) ;
@@ -3900,27 +3906,44 @@ describe("CLI e2e happy path", () => {
39003906 assert . ok ( stderr . includes ( "Config" ) , `stderr should contain human lines, got: ${ stderr } ` ) ;
39013907 } ) ;
39023908
3903- it ( "status has billing, wallet_balance_usd_micros , and project_id fields (GH-32) " , async ( ) => {
3909+ it ( "status exposes wallet identity, grouped balances , and project_id fields" , async ( ) => {
39043910 const { run } = await import ( "./cli/lib/status.mjs" ) ;
39053911 captureStart ( ) ;
39063912 await run ( ) ;
39073913 captureStop ( ) ;
39083914 const out = captured ( ) ;
39093915 const data = JSON . parse ( out ) ;
3910- assert . ok ( data . allowance , "should include allowance" ) ;
3911- assert . ok ( data . allowance . address , "should include allowance address" ) ;
3916+ // Wallet identity uses local_label / server_label (not name / label).
3917+ assert . ok ( data . wallet , "should include wallet" ) ;
3918+ assert . ok ( data . wallet . address , "should include wallet.address" ) ;
3919+ assert . ok ( typeof data . wallet . local_label === "string" , "wallet should include local_label" ) ;
3920+ assert . ok ( Object . prototype . hasOwnProperty . call ( data . wallet , "server_label" ) , "wallet should include server_label" ) ;
3921+ assert . ok ( ! Object . prototype . hasOwnProperty . call ( data . wallet , "name" ) , "wallet should not use legacy name field" ) ;
3922+ assert . ok ( ! Object . prototype . hasOwnProperty . call ( data . wallet , "label" ) , "wallet should not use legacy label field" ) ;
39123923 assert . ok ( Array . isArray ( data . projects ) , "should include projects array" ) ;
3913- // GH-32 sub-issue 2: rename balance → billing, add wallet_balance_usd_micros
3924+ // The duplicate allowance block and the stale funded boolean are gone.
3925+ assert . ok ( ! Object . prototype . hasOwnProperty . call ( data , "allowance" ) ,
3926+ `status should not expose duplicate "allowance" block; got: ${ JSON . stringify ( data ) } ` ) ;
3927+ assert . ok ( ! Object . prototype . hasOwnProperty . call ( data , "funded" ) ,
3928+ `status should not expose stale "funded" boolean; got: ${ JSON . stringify ( data ) } ` ) ;
3929+ // Balances are grouped; no ambiguous top-level balance / wallet_balance_usd_micros / billing.
39143930 assert . ok ( ! Object . prototype . hasOwnProperty . call ( data , "balance" ) ,
39153931 `status should not expose ambiguous "balance" field; got: ${ JSON . stringify ( data ) } ` ) ;
3916- assert . ok ( Object . prototype . hasOwnProperty . call ( data , "billing" ) ,
3917- `status should expose "billing" field; got: ${ JSON . stringify ( data ) } ` ) ;
3918- assert . ok ( Object . prototype . hasOwnProperty . call ( data , "wallet_balance_usd_micros" ) ,
3919- `status should expose "wallet_balance_usd_micros"; got: ${ JSON . stringify ( data ) } ` ) ;
3920- const wb = data . wallet_balance_usd_micros ;
3932+ assert . ok ( ! Object . prototype . hasOwnProperty . call ( data , "wallet_balance_usd_micros" ) ,
3933+ `status should fold on-chain balance into balances; got: ${ JSON . stringify ( data ) } ` ) ;
3934+ assert . ok ( ! Object . prototype . hasOwnProperty . call ( data , "billing" ) ,
3935+ `status should fold prepaid credit into balances; got: ${ JSON . stringify ( data ) } ` ) ;
3936+ assert . ok ( typeof data . balances === "object" && data . balances !== null , "should include balances object" ) ;
3937+ const wb = data . balances . on_chain_usd_micros ;
39213938 assert . ok ( wb === null || typeof wb === "number" ,
3922- `wallet_balance_usd_micros must be number or null; got: ${ JSON . stringify ( wb ) } ` ) ;
3923- // GH-32 sub-issue 4: projects entries must use project_id
3939+ `balances.on_chain_usd_micros must be number or null; got: ${ JSON . stringify ( wb ) } ` ) ;
3940+ assert . equal ( data . balances . on_chain_token , data . rail === "mpp" ? "pathUSD" : "USDC" ,
3941+ `on_chain_token should track rail; got: ${ JSON . stringify ( data . balances ) } ` ) ;
3942+ assert . ok ( Object . prototype . hasOwnProperty . call ( data . balances , "prepaid_credit_usd_micros" ) ,
3943+ "balances should include prepaid_credit_usd_micros" ) ;
3944+ assert . ok ( Object . prototype . hasOwnProperty . call ( data . balances , "held_usd_micros" ) ,
3945+ "balances should include held_usd_micros" ) ;
3946+ // projects entries must use project_id
39243947 for ( const p of data . projects ) {
39253948 assert . ok ( typeof p . project_id === "string" && p . project_id . length > 0 ,
39263949 `each project should have a project_id; got: ${ JSON . stringify ( p ) } ` ) ;
@@ -4049,11 +4072,12 @@ describe("CLI e2e happy path", () => {
40494072 assert . equal ( allowance . rail , "mpp" , "rail should be mpp" ) ;
40504073 } ) ;
40514074
4052- // GH-81: after MPP faucet succeeds, the JSON summary must reflect funded=true
4053- // and the polled balance. Previously `summary.allowance` was captured before
4054- // the faucet branch ran, so the JSON reported `funded: false` and
4055- // `usd_micros: 0` even when the human-readable lines said "funded".
4056- it ( "init mpp reports funded=true after faucet settles (GH-81)" , async ( ) => {
4075+ // GH-81: after MPP faucet succeeds, the JSON summary must reflect the polled
4076+ // on-chain balance. Previously the balance was captured before the faucet
4077+ // branch ran, so the JSON reported `usd_micros: 0` even when the
4078+ // human-readable lines said "funded". On-chain balance now lives under
4079+ // `balances.on_chain_usd_micros` (the old `funded` boolean was removed).
4080+ it ( "init mpp reflects polled on-chain balance after faucet settles (GH-81)" , async ( ) => {
40574081 tempoRpcCallCount = 0 ; // first eth_call returns 0 → triggers faucet path
40584082 const { run } = await import ( "./cli/lib/init.mjs" ) ;
40594083 captureStart ( ) ;
@@ -4062,11 +4086,9 @@ describe("CLI e2e happy path", () => {
40624086 const stdout = capturedStdout ( ) ;
40634087 const parsed = JSON . parse ( stdout ) ;
40644088 assert . equal ( parsed . rail , "mpp" , `rail must be mpp; got: ${ parsed . rail } ` ) ;
4065- assert . equal ( parsed . allowance . funded , true ,
4066- `summary.allowance.funded must be true after successful faucet; got: ${ JSON . stringify ( parsed . allowance ) } ` ) ;
4067- assert . equal ( parsed . balance . symbol , "pathUSD" ) ;
4068- assert . ok ( parsed . balance . usd_micros > 0 ,
4069- `summary.balance.usd_micros must reflect polled balance (>0); got: ${ parsed . balance . usd_micros } ` ) ;
4089+ assert . equal ( parsed . balances . on_chain_token , "pathUSD" ) ;
4090+ assert . ok ( parsed . balances . on_chain_usd_micros > 0 ,
4091+ `balances.on_chain_usd_micros must reflect polled balance (>0) after faucet; got: ${ parsed . balances . on_chain_usd_micros } ` ) ;
40704092 } ) ;
40714093
40724094 it ( "allowance status (MPP rail)" , async ( ) => {
@@ -5020,11 +5042,11 @@ describe("CLI canonical error envelope (GH-215, GH-174)", () => {
50205042
50215043describe ( "CLI status local-state inspection (cli-output-shape)" , ( ) => {
50225044 // Per cli-output-shape spec: absence of local state is an informational
5023- // read, not an error. status exits 0 with `{ allowance : null, hint: "..." }`;
5045+ // read, not an error. status exits 0 with `{ wallet : null, hint: "..." }`;
50245046 // allowance status exits 0 with `{ wallet: null, hint: "..." }`. The
50255047 // previous GH-191 contract (exit 1 + `status: "no_allowance"`) was retired
50265048 // in v3.0 as part of the CLI envelope normalization.
5027- it ( "status with no allowance emits typed null allowance and exits 0" , async ( ) => {
5049+ it ( "status with no allowance emits typed null wallet and exits 0" , async ( ) => {
50285050 const { ALLOWANCE_FILE } = await import ( "./cli/lib/config.mjs" ) ;
50295051 try { rmSync ( ALLOWANCE_FILE , { force : true } ) ; } catch { }
50305052 const { run } = await import ( "./cli/lib/status.mjs" ) ;
@@ -5041,7 +5063,7 @@ describe("CLI status local-state inspection (cli-output-shape)", () => {
50415063 assert . ok ( line , `should emit payload on stdout, got: ${ stdout } ` ) ;
50425064 const parsed = JSON . parse ( line ) ;
50435065 assert . equal ( parsed . status , undefined , "must not emit a top-level status field" ) ;
5044- assert . equal ( parsed . allowance , null , "allowance must be typed null when absent" ) ;
5066+ assert . equal ( parsed . wallet , null , "wallet must be typed null when absent" ) ;
50455067 assert . ok ( parsed . hint && / r u n 4 0 2 i n i t / . test ( parsed . hint ) , `hint must guide to next step, got: ${ parsed . hint } ` ) ;
50465068 } ) ;
50475069
@@ -5161,7 +5183,7 @@ describe("CLI malformed allowance.json (GH-194)", () => {
51615183 `message should mention privateKey; got: ${ parsed . message } ` ) ;
51625184 } ) ;
51635185
5164- it ( "status with unparseable allowance.json still surfaces as typed null allowance (existing UX preserved)" , async ( ) => {
5186+ it ( "status with unparseable allowance.json still surfaces as typed null wallet (existing UX preserved)" , async ( ) => {
51655187 const { ALLOWANCE_FILE } = await import ( "./cli/lib/config.mjs" ) ;
51665188 const fs = await import ( "node:fs" ) ;
51675189 fs . writeFileSync ( ALLOWANCE_FILE , "not json" ) ;
@@ -5180,8 +5202,8 @@ describe("CLI malformed allowance.json (GH-194)", () => {
51805202 assert . ok ( line , `should emit payload on stdout, got: ${ stdout } ` ) ;
51815203 const parsed = JSON . parse ( line ) ;
51825204 assert . equal ( parsed . status , undefined , "must not emit a top-level status field" ) ;
5183- assert . equal ( parsed . allowance , null ,
5184- `unparseable JSON should surface as typed null allowance (no BAD_ALLOWANCE_FILE error envelope); got: ${ JSON . stringify ( parsed ) } ` ) ;
5205+ assert . equal ( parsed . wallet , null ,
5206+ `unparseable JSON should surface as typed null wallet (no BAD_ALLOWANCE_FILE error envelope); got: ${ JSON . stringify ( parsed ) } ` ) ;
51855207 } ) ;
51865208} ) ;
51875209
0 commit comments