taximeter 0.2.1 → 0.3.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Taximeter
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Show effective configuration and source layers in doctor, add validated atomic config editing with exact human amount inputs, and expose temporary start overrides. Enforce optional payment-count budgets using a schema-3 incremental cache with pre-migration backups, and return executable advisory fix hints for blocked payments. The dashboard remains read-only and displays count usage alongside amount usage.
8
+
9
+ ## 0.2.2
10
+
11
+ ### Patch Changes
12
+
13
+ - Print a one-time stderr notice when the first encrypted CONNECT tunnel arrives, while preserving per-tunnel diagnostics and forwarding behavior. Add a manual Base Sepolia x402 verification workflow and document measured storage-reduction options.
14
+
3
15
  ## 0.2.1
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -10,14 +10,20 @@ Taximeter records supported agent payments, checks budgets before forwarding the
10
10
  and keeps an exact local ledger. Install from npm with the command above, or build
11
11
  from source with the quickstart below. Requires Node 20 or newer.
12
12
 
13
+ Built with AI, with an auditable record of [protocol corrections](https://github.com/Ding808/taximeter/blob/main/SPEC-NOTES.md), [decisions](https://github.com/Ding808/taximeter/blob/main/DECISIONS.md), [verification](https://github.com/Ding808/taximeter/blob/main/VERIFICATION.md), and [reproducible benchmarks](https://github.com/Ding808/taximeter/tree/main/benchmarks).
14
+
13
15
  ## A 20-second demo
14
16
 
15
- ![Taximeter allows 20 payments and blocks payment 21](docs/demo.gif)
17
+ ![Taximeter meters a real testnet payment and blocks the next payment](docs/demo.gif)
16
18
 
17
- This local recording shows 20 payments passing and payment 21 being blocked at
18
- an exact total of 140 atomic units. It uses synthetic x402 envelopes and never
19
- moves money. [Record the demo](https://github.com/Ding808/taximeter/blob/main/docs/RECORDING.md)
20
- with the simulation in the source checkout.
19
+ This recording sends **0.001 test USDC on Base Sepolia** through the actual CLI
20
+ proxy, verifies its canonical receipt and balance changes, then blocks the next
21
+ signed request at an exact budget of 1000 atomic units.
22
+ [View the transaction](https://sepolia.basescan.org/tx/0x10dc76728133356363ad1dd8d694a5e1eee2086fc3325ca572d3d035e7062c58)
23
+ or [reproduce the recording](https://github.com/Ding808/taximeter/blob/main/docs/RECORDING.md).
24
+ The official client signs and the facilitator settles; Taximeter observes and
25
+ gates. This proves one testnet flow with a local HTTP seller, not mainnet or
26
+ payment visibility inside HTTPS CONNECT.
21
27
 
22
28
  ## Why
23
29
 
@@ -51,7 +57,7 @@ Open a terminal in this source checkout, with Node 20+ and npm installed.
51
57
  You should now see:
52
58
 
53
59
  ```text
54
- Taximeter 0.2.1
60
+ Taximeter 0.3.0
55
61
  Proxy: http://127.0.0.1:8402
56
62
  Dashboard: http://127.0.0.1:8403
57
63
  Point an HTTP-proxy-aware agent at http://127.0.0.1:8402.
@@ -70,7 +76,7 @@ Choose the connection mode your agent supports:
70
76
  | HTTP forward proxy | Configure the client's HTTP proxy as `http://127.0.0.1:8402`. `HTTP_PROXY` works only in clients that honor it. | Plain HTTP payment requests and replies. |
71
77
  | Explicit upstream | Start with `--upstream` set to the real HTTP(S) API origin, then use the local proxy URL as the agent's API base URL. | HTTP or HTTPS upstream payments, without intercepting TLS. |
72
78
  | SDK | Put `withMeter(fetch, options)` **inside** the payment wrapper. See [SDK examples](docs/SDK.md). | Requests made through the supplied transport. |
73
- | HTTPS CONNECT | A proxy-aware HTTPS client may open a tunnel. | Encrypted bytes pass through, with an unmetered diagnostic. Payment headers are invisible. |
79
+ | HTTPS CONNECT | A proxy-aware HTTPS client may open a tunnel. | Encrypted bytes pass through. The first tunnel prints a stderr notice; every tunnel records an unmetered diagnostic. Payment headers are invisible. |
74
80
 
75
81
  In upstream mode, `/data` replaces any path prefix in the configured upstream URL.
76
82
  Use the API origin as the upstream and keep its path in the request. Native fetch
@@ -89,9 +95,105 @@ or `txm` command. The CLI provides:
89
95
  | `taximeter export --csv statement.csv` | A new CSV file; refuses to overwrite an existing file. |
90
96
  | `taximeter export --json statement.json` | Event history, raw authorizations, and totals. With no format, JSON goes to stdout. |
91
97
  | `taximeter export --invoice statement.html` | A printable HTML spending statement. |
92
- | `taximeter doctor` | Local configuration and SQLite checks; no network probes. |
98
+ | `taximeter doctor [--json]` | Effective configuration, source layers, and SQLite checks; no network probes. |
99
+ | `taximeter config path` | Configuration layers and the default file to edit. |
100
+ | `taximeter config show [--json]` | The merged effective configuration. |
101
+ | `taximeter config get <key>` | One effective value by dot path. |
102
+ | `taximeter config set <key> <value>` | Validate and save a setting; restart to apply it. |
103
+ | `taximeter config unset <key>` | Remove a file override and inherit the lower layer. |
93
104
  | `taximeter reset --yes` | Archives the ledger. Stop **all CLI and SDK writers** first. |
94
105
 
106
+ ## Changing limits
107
+
108
+ Check the effective configuration first, then change the limit and restart:
109
+
110
+ ```sh
111
+ taximeter doctor
112
+ taximeter config set budgets.global.amount 200USDC
113
+ taximeter config set budgets.perTask.maxPayments 200
114
+ taximeter config set budgets.perAgent.maxPayments 1000
115
+ taximeter config get budgets.global.amount
116
+ # 200000000
117
+ taximeter start
118
+ ```
119
+
120
+ Stop the previous process before starting it again. There is no hot reload.
121
+ `doctor --json` includes the same resolved configuration used by that command,
122
+ the source layers, and local checks. It does not inspect an already-running
123
+ process or recover the flags used to start it. `config show --json` prints just
124
+ the resolved configuration.
125
+
126
+ Use human units for amounts: `5USDC`, `"5 USDC"`, and `5usdc` all store
127
+ `"5000000"`; `0.001USDC` stores `"1000"`. A bare integer such as `5000000` is
128
+ always an atomic-unit value. Conversion uses the offline asset registry and
129
+ rejects unknown symbols, mismatched assets, or excess decimal places; it never
130
+ guesses decimals or rounds. For an unknown asset, use an exact atomic integer.
131
+ Payment counts are positive integers without units.
132
+
133
+ Amount budgets stop spending too much. Payment-count budgets stop an agent from
134
+ making an excessive number of tiny payments while spending little. Each scope
135
+ checks amount first, then count, with the same asset, network, and rolling window.
136
+ Unknown settlements reserve both; blocked or known-failed payments consume neither.
137
+ Zero-amount payments still count. Reusing the same reservation does not count twice.
138
+ Count limits are optional and off by default; they are rolling budgets, not a
139
+ payments-per-second rate limiter.
140
+
141
+ Keep conservative defaults in `~/.taximeter/config.json`, then override selected
142
+ values per project in `taximeter.config.json`. Files merge **key by key**, so a
143
+ project amount change retains the home's count limit and window. The priority is:
144
+ flags → environment → `--config` → cwd file → home file → built-in defaults.
145
+ `config path` and `doctor` show which files exist, which contribute, and which
146
+ keys are overridden. An omitted field inherits; `unset` removes the target file's
147
+ override. `null` disables a whole budget or the nullable single-payment cap:
148
+
149
+ ```sh
150
+ taximeter config set policy.maxSinglePayment 0.5USDC
151
+ taximeter config set policy.allowHosts api.example.com,data.example.com
152
+ taximeter config set budgets.perTask null
153
+ taximeter config unset policy.allowHosts
154
+ ```
155
+
156
+ An empty allow-list allows all hosts or recipients. A populated allow-list allows
157
+ only its listed entries; host matching is exact and case-insensitive.
158
+
159
+ `set` and `unset` write the existing cwd file, otherwise the home file. Use
160
+ `--file <path>` to choose a destination. `--config <path>` selects an additional
161
+ **read layer**, so pass `--file` too when editing that explicit file. Writes validate
162
+ both the patch and the merged result before replacing the file atomically through
163
+ a temporary file in the same directory, created with mode `0o600`. Invalid values
164
+ or unknown keys leave the original file unchanged; misspelled keys get a suggestion.
165
+
166
+ For a single run, override limits without saving them:
167
+
168
+ ```sh
169
+ taximeter start --budget-global 200USDC --max-payments-global 1000 --max-single 0.5USDC
170
+ taximeter start --allow-host api.example.com --allow-host data.example.com
171
+ ```
172
+
173
+ Also available: `--budget-task`, `--budget-agent`, `--max-payments-task`,
174
+ `--max-payments-agent`, and repeatable `--deny-host`. Host flags replace that
175
+ list for the run. Startup prints every active flag override. A flag can enable
176
+ a disabled budget; when no asset is inherited, it uses USDC.
177
+
178
+ A fully resolved budget can contain only `maxPayments`, with no amount limit.
179
+ Because file omissions inherit lower settings, adding `maxPayments` alone does
180
+ not remove an inherited amount limit. To establish a count-only budget across
181
+ layers, disable that scope in the lower file, then set `{ "asset": "USDC",
182
+ "maxPayments": 200 }` in the higher file. Alternatively, disable the stored scope
183
+ and enable it for one run with `--max-payments-global 200` (or its task/agent flag).
184
+ Use `doctor` to confirm the resulting amount and window before starting.
185
+
186
+ Blocked responses include an advisory `fix` command; the terminal prints it once
187
+ per reason per process. Numeric hints raise the affected bound enough for that
188
+ payment. At the largest representable limit, the hint disables the affected
189
+ budget. Host/recipient hints clear the relevant list, and `unknown_asset` suggests
190
+ an explicit opt-in. Review those policy changes before running them. Hints never
191
+ execute automatically. After editing, restart without conflicting override flags;
192
+ when using `--config`, edit the selected file with `--file` as needed.
193
+
194
+ **The dashboard is read-only by design.** It shows amount and payment-count usage
195
+ but has no configuration write endpoints: a web page cannot relax spending limits.
196
+
95
197
  ## How it works
96
198
 
97
199
  ```text
@@ -116,7 +218,7 @@ their amount before forwarding the payment replay.
116
218
  Policy evaluation and reservation share one SQLite transaction, so concurrent
117
219
  payments cannot all consume the same remaining capacity.
118
220
  Append-only payment, outcome, and diagnostic records derive every total with BigInt.
119
- Payment checks use an incrementally maintained index of exact budget sums, avoiding
221
+ Payment checks use an incrementally maintained index of exact budget sums and counts, avoiding
120
222
  a replay of all prior payments. The index can be rebuilt from the original log.
121
223
  The local dashboard polls that ledger every second and exports the same counted amounts.
122
224
 
@@ -140,16 +242,18 @@ as text to prevent a spreadsheet application from rounding them.
140
242
 
141
243
  ## Configuration
142
244
 
143
- When upgrading from 0.1.x, upgrade all writers together. Opening an existing
144
- ledger prints `Migrating ledger…`, saves a standalone schema-1 backup beside it
145
- at `<database>.backup-v1-<unique suffix>/ledger.db`, and prints the backup path
146
- before building the budget index. The backup includes committed WAL records;
245
+ Before upgrading, stop **all proxy and SDK writers**, then upgrade them together.
246
+ Opening a schema-1 (0.1.x) or schema-2 (0.2.x) ledger prints `Migrating ledger…`,
247
+ saves a standalone backup beside it at
248
+ `<database>.backup-v<original schema>-<unique suffix>/ledger.db`, and prints the
249
+ backup path before migration. Schema 3 adds payment counts to the existing
250
+ prefix index and rebuilds both projections from the audit log. The backup includes committed WAL records;
147
251
  if it cannot be completed, the upgrade stops. Large ledgers take longer on this
148
252
  first open. Fresh databases and already-upgraded ledgers do not create backups.
149
253
 
150
- The source log is preserved, but older 0.1.x clients cannot reopen the upgraded
254
+ The source log is preserved, but older 0.1.x/0.2.x clients cannot reopen the upgraded
151
255
  database. To roll back, stop all CLI and SDK writers, copy the saved `ledger.db`
152
- to a **new database path**, and start 0.1.x with `--db` pointing there. Keep the
256
+ to a **new database path**, and start the matching older version with `--db` pointing there. Keep the
153
257
  upgraded database: the backup does not contain payments made after migration.
154
258
  Backups are retained until you remove them; a `ledger.partial.db` file means the
155
259
  backup did not finish and must not be used for rollback. Progress goes to stderr,
@@ -167,7 +271,8 @@ invalid values are rejected with Zod.
167
271
  | `budgets.perTask` | `{"amount":"5000000","asset":"USDC"}` | 5 USDC per task, all time. `null` disables it. |
168
272
  | `budgets.perAgent` | `{"amount":"50000000","asset":"USDC","window":"24h"}` | 50 USDC per agent in a rolling 24 hours. `null` disables it. |
169
273
  | `budgets.global` | `{"amount":"100000000","asset":"USDC","window":"24h"}` | 100 USDC per network/asset in a rolling 24 hours. `null` disables it. |
170
- | Each budget's `amount` | Scope default above | Nonnegative integer string in atomic units, at most 78 digits. |
274
+ | Each budget's `amount` | Scope default above | Optional nonnegative integer string in atomic units, at most 78 digits. At least `amount` or `maxPayments` must be present in a resolved budget. |
275
+ | Each budget's `maxPayments` | Omitted | Optional positive safe integer, at most 9007199254740991. Counts the same payments as spend within the scope's window. |
171
276
  | Each budget's `asset` | `USDC` | Trusted local symbol or exact contract address. |
172
277
  | Each budget's `network` | Omitted | Optional exact network such as `eip155:8453`; otherwise evaluate each network separately. |
173
278
  | Each budget's `window` | Scope default above | `1h`, `24h`, `7d`, or `30d`; omitted means all time. |
@@ -207,12 +312,15 @@ A denied replay receives HTTP 402 before it reaches the upstream. For a cap of
207
312
  "reason": "global_budget",
208
313
  "budget": "140",
209
314
  "spent": "140",
210
- "remaining": "0"
315
+ "remaining": "0",
316
+ "fix": "taximeter config set budgets.global.amount 147"
211
317
  }
212
318
  ```
213
319
 
214
320
  Reasons are `host_denied`, `host_not_allowed`, `recipient_not_allowed`, `unknown_asset`,
215
- `max_single_payment`, `per_task_budget`, `per_agent_budget`, and `global_budget`.
321
+ `max_single_payment`, `per_task_budget`, `per_agent_budget`, `global_budget`,
322
+ `per_task_payment_count`, `per_agent_payment_count`, and `global_payment_count`.
323
+ Count denials use decimal count strings in `budget`, `spent`, and `remaining`.
216
324
  Host, recipient, and unknown-asset denials have `budget: null`, `spent: "0"`, and
217
325
  `remaining: null`.
218
326
 
@@ -228,6 +336,12 @@ Raw payment authorizations stay in the local audit ledger and JSON export. Treat
228
336
  them as sensitive. [Security guidance](SECURITY.md) describes the boundary and how
229
337
  to report a problem.
230
338
 
339
+ Storage grows with payment, retry, and diagnostic history. There is currently no
340
+ retention, prune, or compact command; `reset` archives the old database and does
341
+ not reclaim its disk space. Large-ledger users should monitor available space.
342
+ The [storage measurements and follow-up plan](https://github.com/Ding808/taximeter/blob/main/docs/STORAGE.md)
343
+ describe the measured costs and migration constraints.
344
+
231
345
  ## Contributing / license
232
346
 
233
347
  See the [contribution guide](https://github.com/Ding808/taximeter/blob/main/CONTRIBUTING.md)