taximeter 0.1.1 → 0.1.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.
package/CONTRIBUTING.md DELETED
@@ -1,141 +0,0 @@
1
- # Contributing to Taximeter
2
-
3
- Taximeter observes agent payments, records them locally, and gates supported
4
- authorizations against budgets. Start with [SPEC.md](SPEC.md), then read
5
- [SPEC-NOTES.md](SPEC-NOTES.md) for the supported protocol and
6
- [DECISIONS.md](DECISIONS.md) for implementation choices.
7
-
8
- ## Development
9
-
10
- Use Node.js 20 or 22 and npm. The package is TypeScript with ESM exports. CI is
11
- configured for both Node versions on Linux and Windows. Dependencies and tooling
12
- are pinned; keep `package-lock.json` consistent with `package.json`.
13
-
14
- From a checkout, run the same sequence as CI:
15
-
16
- ```sh
17
- npm ci
18
- npm run typecheck
19
- npm run lint
20
- npm test
21
- npm run build
22
- npm pack
23
- npm run check:package
24
- npm run smoke:package
25
- git status --porcelain
26
- ```
27
-
28
- The build creates the Node package in `dist/` and the prebuilt React dashboard in
29
- `dist/ui/`. A package consumer must not need to build the dashboard. On a clean
30
- checkout, the final command must print nothing. During development, it should
31
- show only your intentional changes. Do not commit generated bundles, coverage,
32
- tarballs, local configuration, or SQLite state.
33
-
34
- Use `npm run format` to apply Biome formatting. Keep LF line endings as specified
35
- in `.gitattributes`. Check the package contents and compressed size with
36
- `npm pack --dry-run`; the release tarball must stay below 2 MB.
37
-
38
- ## Changes that preserve trust
39
-
40
- - Preserve unknown HTTP traffic byte for byte. Record a diagnostic when a format
41
- cannot be parsed; do not guess the payment fields or rewrite its payload.
42
- - Store monetary amounts as integer strings. Use `BigInt` for arithmetic and
43
- convert to a display string only at the rendering edge. Never combine assets
44
- or networks, infer exchange rates, or use floating point for money.
45
- - Parse external inputs with Zod. Keep strict TypeScript types and
46
- `noUncheckedIndexedAccess`; do not introduce `any` to silence a type error.
47
- - Keep policy and ledger derivations pure and synchronous. Evaluate policy and
48
- reserve capacity within the same SQLite transaction before forwarding.
49
- - Keep events and outcomes append-only. Preserve uncertainty, retry identity,
50
- concurrent attempt accounting, and original attribution.
51
- - Never add custody, private-key handling, payment signing, settlement,
52
- telemetry, hosted accounts, or runtime metadata lookups. Runtime networking
53
- is limited to forwarding the caller's traffic and the local dashboard.
54
-
55
- Read the primary x402 specification and published package types before changing
56
- a wire parser. Record changed assumptions and supported subsets in
57
- `SPEC-NOTES.md`. An internal model field is not necessarily a protocol field.
58
-
59
- ## Tests and review
60
-
61
- For policy changes, write the failing behavioral test first. Add regressions for
62
- traffic corruption, arithmetic, replay, concurrency, and settlement failures when
63
- changing those paths. Keep policy and ledger coverage at least 90%; coverage is
64
- a floor, not a substitute for asserting observable behavior.
65
-
66
- Fixtures use synthetic authorizations and localhost servers. Tests must not
67
- require wallets, keys, funds, a facilitator, or an external API. Keep cleanup
68
- bounded to directories created by the test. Dashboard changes should be checked
69
- in light and dark themes at both desktop and narrow mobile widths.
70
-
71
- Golden CSV snapshots live in `test/__snapshots__/`. Regenerate a snapshot only
72
- for an intentional output change that is explained in the pull request and
73
- accepted in review. Never update a snapshot, remove a test, or relax an assertion
74
- simply to make a failure disappear.
75
-
76
- Open a focused pull request with the problem, resulting behavior, and actual
77
- validation results. Explain compatibility changes and known limitations. Use
78
- Conventional Commits, such as `fix(proxy): preserve response trailers`. Update
79
- the relevant docs when behavior or configuration changes. Report vulnerabilities
80
- privately as described in [SECURITY.md](SECURITY.md).
81
-
82
- ## Versioning and releases
83
-
84
- For a user-visible change, run `npm run changeset` and commit the generated
85
- changeset with the change. Describe its effect on users and choose the appropriate
86
- version bump. Documentation-only changes may omit a changeset when they do not
87
- change package behavior.
88
-
89
- The release workflow uses Changesets on `main` to prepare version changes and
90
- publish after the release pull request is merged. With no pending changesets,
91
- the action also publishes the current version if npm does not yet contain it.
92
- A push to `main` with working npm credentials can therefore publish directly.
93
- Maintainers configure npm credentials separately.
94
- `npm run version-packages` updates package versions and
95
- the changelog; `npm run release` builds and runs Changesets publishing. Publishing
96
- requires maintainer authorization and credentials. The presence of that workflow
97
- does not mean any package version has already been published.
98
-
99
- ### npm publishing credentials
100
-
101
- The current workflow uses token authentication. Create an npm **granular access
102
- token** with package **Read and write** permissions and **Bypass two-factor
103
- authentication** enabled for unattended publishing. Give it permission to publish
104
- the intended package and an appropriate expiration. Follow the
105
- [npm token setup instructions](https://docs.npmjs.com/creating-and-viewing-access-tokens/).
106
-
107
- For the first publication of a new unscoped package, it cannot yet be selected
108
- individually in npm's token settings. A temporary bootstrap token needs the
109
- **All Packages** selection to permit creating it. Replace it with a token limited
110
- to `taximeter` after the package exists, or use trusted publishing for later releases.
111
-
112
- In the repository's **Settings → Secrets and variables → Actions**, add a
113
- **repository secret** named exactly `NPM_TOKEN`. Use the token as its value.
114
- An Actions variable or a Dependabot secret will not populate `secrets.NPM_TOKEN`.
115
- An environment secret needs a matching job environment; this workflow does not
116
- select one. Organization secrets must grant this repository access. See
117
- [GitHub's Actions secret instructions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions).
118
-
119
- `changesets/action@v1` configures npm authentication from this secret. Its publish
120
- hook checks that the secret is present and that npm accepts the credentials
121
- before building or publishing. This identity check does not prove package write
122
- permission or 2FA bypass; npm validates those during publication. Version PRs
123
- can still be prepared without npm credentials. Do not commit a token to `.npmrc`
124
- or paste one into an issue or chat.
125
-
126
- After adding or correcting the secret, rerun the failed Release job. A local
127
- `npm login` does not authenticate a GitHub-hosted runner. `ENEEDAUTH` combined
128
- with `No NPM_TOKEN or OIDC available` means the runner received no usable
129
- authentication; the successful build and dependency-comment warnings do not
130
- resolve that missing configuration.
131
-
132
- Trusted publishing is a separate setup, not enabled by adding `GITHUB_TOKEN`.
133
- It requires npm-side trust, a supported npm CLI, and `id-token: write` on the
134
- workflow. The present hook deliberately requires `NPM_TOKEN`; migrate both the
135
- hook and workflow together when adopting
136
- [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/).
137
- The package must already exist before trust can be configured; see the
138
- [npm trust prerequisites](https://docs.npmjs.com/cli/v11/commands/npm-trust/).
139
-
140
- Contributions are provided under the project's [MIT license](LICENSE). Follow the
141
- [code of conduct](CODE_OF_CONDUCT.md) in issues, reviews, and other project spaces.
package/DECISIONS.md DELETED
@@ -1,54 +0,0 @@
1
- # Decisions
2
-
3
- - 2026-09-08 — The hosted release log passed the build but failed with npm `ENEEDAUTH`; Changesets explicitly reported neither `NPM_TOKEN` nor OIDC. Keep the existing token-based authentication route and add a publish-only presence/identity check, preserving credential-free version PR creation. Document the exact repository-secret setup. A local code change cannot supply the owner's npm credential, and an identity check cannot attest package write permissions.
4
- - 2026-09-08 — Replace the SPEC §6 demo placeholder with a 20-second Windows terminal recording. Native ConPTY capture and the standalone agg renderer work without WSL. Only the compact GIF ships in the package; capture tools and intermediate files remain local development artifacts.
5
- - Release packaging includes all relative README/SDK/contributor documentation targets and the example configuration. The package checker verifies these links and the explicit whitelist; source trees, tests, local state, and UI source maps stay excluded.
6
- - The release workflow can publish the first unpublished version on a credentialed `main` push when no changesets remain. Contributor instructions identify this initial-release behavior; no push or registry publication is performed during local verification.
7
- - The demo's unpaid v2 challenge branch is exercised before its signed synthetic replays, including the required `resource.url`, so demonstration code follows the same verified protocol contract.
8
-
9
- Recorded through the implementation audit on **2026-09-08**. The consolidated
10
- deviation inventory and detailed protocol evidence are in
11
- [SPEC-NOTES.md](SPEC-NOTES.md#implemented-deviations-and-release-limits).
12
- This log records the reasons for those choices and corrections made during the
13
- build; successful verification output belongs in [VERIFICATION.md](VERIFICATION.md).
14
-
15
- - 2026-09-08 — Work follows SPEC §8 on `feat/taximeter-v0.1.0`; each numbered step gets a full passing suite and a Conventional Commit.
16
- - Pin better-sqlite3 12.8.0: version 13 requires Node 22, while this release promises Node 20 support.
17
- - ESM only: a second CJS runtime adds packaging complexity without serving the primary CLI or modern fetch integrations.
18
- - Runtime requests are limited to forwarding traffic and the local dashboard API. Development dependency installation and protocol research are build activities.
19
- - Biome's automatic configuration migration emitted `preset: none`; corrected it to `recommended` immediately so lint remains a real quality gate.
20
- - Initial dependency audit found advisories in uuid 11.1.0 and tsup's esbuild 0.27.x. Pin uuid 11.1.1 and override tsup's esbuild to 0.28.1; validate with the full suite and build. Keep Changesets 2.x for Node 20 compatibility.
21
- - SQLite adds append-only `outcomes` and `diagnostics` tables beside `events`; this preserves original payment authorizations while recording settlement uncertainty and parser diagnostics. The migration is bundled as text from the tracked SQL source.
22
- - Idempotency binds the network/asset/payer/nonce identity to immutable authorization details; conflicting authorizations receive separate conservative reservations rather than bypassing policy through nonce reuse.
23
- - Zod 4 applies inner defaults even through `.partial()`. Configuration patch schemas therefore have no defaults; a regression test verifies later layers cannot reset earlier sibling options.
24
- - Independent ledger tests initially expected checksum-case output and `settlement_unknown: true` on blocked rows. Correct those expectations: EVM identities are canonical lowercase, and a replay blocked before forwarding has no uncertain settlement. Exact-value and deduplication assertions remain intact.
25
- - Track individual forwarding attempts in append-only outcomes. Any unresolved attempt retains capacity; confirmation anywhere wins. An unconfirmed retry outside a rolling window must reacquire capacity, with `attemptedAt` separate from the original immutable timestamp.
26
- - Runtime storage failures fail open and emit a visible local stderr warning: preserving the agent's traffic is the stated highest priority. Budget guarantees require working local storage. The original reservation survives a failed outcome write.
27
- - CLI precedence is flags > environment > explicit `--config` file > cwd file > home file > defaults. Relative database paths resolve against cwd; `~` expands locally. Explicit config files layer rather than replace lower-priority settings.
28
- - `reset --yes` archives the database under an atomic CLI lock. Stop all SDK writers first; the CLI lock coordinates CLI instances, while SQLite coordinates payment reservations. Reports, exports, and doctor use an in-memory empty ledger when the requested path does not yet exist, avoiding accidental file creation.
29
- - CSV includes every derived payment row. `amount` is its counted contribution (zero for blocked rows and authorizations whose known attempts all failed); `authorizedAmount` preserves the original proposal. Sum `amount` separately by network/asset to reproduce dashboard ledger totals. Formula-like text cells are escaped; monetary cells remain exact integer strings. Invoice export is a standalone printable HTML statement, with no PDF generation or claim of independent settlement verification.
30
- - SDK inspection clones only legacy 402 bodies, capped at 64 KiB and 100 ms, then returns the original response object. Slow/oversized unknown challenges are diagnosed and passed through. Request bodies are never consumed by the meter. Put `withMeter` inside the payment wrapper so signed retries are visible.
31
- - Dashboard reads share the ledger derivations and exact CSV accounting. The loopback API rejects foreign Host/Origin values, cross-site requests, mutations, ambiguous duplicate query options, and paths outside its bundled UI directory.
32
- - Dashboard typography uses system grotesque and monospace stacks: remote Google Fonts would violate the runtime network invariant. Financial chart coordinates and proportional widths use BigInt and become strings only when rendered.
33
- - The SDK cannot observe redirects or retries inside the supplied fetch transport. Preserve transport behavior, diagnose visible redirects, and avoid correlating redirected v1 challenges to the initial URL. Host-sensitive callers can request `redirect: "error"` or expose each hop to the wrapper.
34
- - Release review found truncated rejected-upgrade responses could leave clients hanging. Add a failing regression first, then mirror the ordinary response's abort/error cleanup.
35
- - Preserve the original HTTP request path and query while validating the routing authority separately. WHATWG URL normalization of encoded dot segments would otherwise change opaque or signature-sensitive traffic; HTTP and upgrade regressions cover both proxy modes.
36
- - The first dashboard shows the latest 30 events and a trailing 24-hour timeline. Rows remain readable and tables scroll on shorter or narrow screens; exact totals and clear settlement labels take priority over fitting every row into every viewport.
37
-
38
- ## Scope and release audit
39
-
40
- - Support `exact` EVM EIP-3009 only: v1 `base` and `base-sepolia`, and v2 positive `eip155:<chain-id>` networks. Permit2, ERC-7710, and other transfer methods have different authorization semantics and stay transparent with diagnostics. Optional or variable-length signatures are structurally accepted according to the published 2.25.0 types; Taximeter never verifies them.
41
- - Bound v1 challenge context to URL, method, task, agent, Authorization, and Cookie, with a five-minute TTL and at most 1,000 contexts per intake instance. Require an unambiguous matching asset. This avoids inventing missing replay fields or sharing challenges across distinct request identities. The SDK does not cache redirected challenges under the original URL.
42
- - Limit inspected v1 bodies to 64 KiB and encoded payment header text to 65,536 characters. The SDK additionally stops challenge observation after 100 ms. The proxy streams its bounded body tap without holding the response; compressed legacy challenges pass through unmetered. These limits prioritize intact, responsive traffic over metering every possible challenge.
43
- - Keep local metadata only for Base and Base Sepolia USDC. Unknown assets use exact atomic strings with `decimalsKnown: false`; `decimals: 0` is an internal placeholder, never a token-metadata claim. Runtime metadata, RPC, and price calls would violate the network invariant.
44
- - Keep every budget separate by network and asset contract. `maxSingleAsset: "USDC"` makes the scalar per-payment cap's scope explicit, and optional budget `network` narrows a rule. Add nullable budgets/caps and fixed `1h`, `24h`, `7d`, and `30d` windows without introducing exchange rates or cross-chain combined balances.
45
- - Matching settlement evidence must use the event's network and, when provided, its payer and amount. A reported success with a nonempty transaction can confirm on any response status; an explicit failure on a 5xx remains uncertain. Missing evidence, disconnects, and malformed responses retain the reservation. Confirmation on any attempt wins over later contradictions; otherwise an unresolved attempt wins over a failed attempt.
46
- - Interpret explicit `upstream` as an origin: an incoming origin-form path replaces any configured path prefix. Preserve encoded path/query bytes while keeping leading `//` on that fixed origin. This rule is documented so callers do not silently route `/weather` to an assumed `/v1/weather`.
47
- - Forward HTTPS CONNECT as an unmetered encrypted tunnel, with no certificate installation or TLS interception. Gate recognized signed HTTP upgrade handshakes but do not claim to inspect subsequent stream frames. Payload-byte transparency excludes hop-by-hop headers and transfer framing that an HTTP proxy must reconstruct.
48
- - Keep the dashboard timeline to the current UTC hour plus the preceding 23 hourly buckets; do not invent a persistent process-session model. Show each asset's active global budget separately from the all-time ledger total. Show 30 recent events and 10 recent diagnostics; keep original payment JSON in JSON export rather than the live summary API.
49
- - Add `TAXIMETER_DB`, `TAXIMETER_PORT`, and `TAXIMETER_DASHBOARD_PORT` between explicit flags and configuration files. This follows the brief's environment-variable naming while making precedence explicit. Allow port `0` for local tests and ephemeral listeners. An injected SDK ledger uses supplied configuration plus defaults, avoiding hidden user-file dependencies for embedded callers.
50
- - `doctor` is deliberately local. With a nonexistent database it checks an empty in-memory SQLite ledger and configuration; it does not claim to test that future path's write permissions, wallet access, or upstream/facilitator readiness. Existing malformed config remains a visible error.
51
- - Protocol fixtures use deterministic synthetic authorizations and settlement results derived from documented envelopes. A structural parser test is not a real payment, wallet integration, or chain-settlement test. Record this boundary instead of presenting synthetic fixture success as live-network proof.
52
- - Rechecked the npm name on **2026-09-08**: `npm view taximeter` returned `E404`, so no fallback name was needed. The README keeps the required `npx taximeter start` command and labels registry publication as pending; source quickstart commands use `npm ci`, `npm run build`, and `node dist/cli/index.js start`. Validate the local `taximeter-0.1.0.tgz` in a fresh temporary directory before claiming packaged startup; record the actual result in `VERIFICATION.md`.
53
- - Generate the initial `CHANGELOG.md` with Changesets using a transient, uncommitted `0.0.0` version and an initial minor changeset, then restore the intended `0.1.0` through `npm run version-packages` and resynchronize the lockfile. This produces a generated changelog without changing the release version or rewriting commit history. The demo uses local synthetic payments: its recording script demonstrates 20 accepted payments and a blocked twenty-first payment without making a transfer. The initial GIF placeholder was subsequently replaced by the Windows recording documented above.
54
- - Repository visibility, description, and npm publication are maintainer-managed release settings. The intended repository description is **A taximeter for your AI agents.** A successful local build does not establish that registry publication or repository configuration has completed.
package/SPEC-NOTES.md DELETED
@@ -1,369 +0,0 @@
1
- # Protocol verification notes
2
-
3
- Research date: **2026-09-08**. These notes record the protocol baseline read before
4
- implementation, the supported subset, and corrections to [SPEC.md](SPEC.md).
5
- Amounts below are atomic integer strings. Taximeter does not sign, verify, or
6
- settle a payment.
7
-
8
- ## Implemented deviations and release limits
9
-
10
- This is the release's consolidated deviation inventory. The detailed wire shapes
11
- and source evidence follow below; these limits apply to both the CLI and SDK
12
- unless a row names one intake specifically.
13
-
14
- | Area in the brief | Implemented behavior and reason |
15
- | --- | --- |
16
- | Demo placeholder (§6) | The original placeholder is replaced by a 20-second Windows ConPTY recording rendered by agg. The simulation runs locally; no payment is signed or settled. Only the GIF ships in the package; capture tools and intermediate files are local development artifacts. |
17
- | Broad x402 support (§2, §4) | Support HTTP v1/v2 `exact` EVM **EIP-3009** authorizations. v1 maps only `base` and `base-sepolia`; v2 accepts positive `eip155:<chain-id>` identifiers. Permit2, ERC-7710, other transfer methods, schemes, and networks are diagnosed and passed through. Their different authorization semantics cannot safely share this parser. |
18
- | Generic 402 JSON and complete replay context (§0, §2) | v1 reads the challenge body and `X-PAYMENT`; v2 reads `PAYMENT-REQUIRED` and `PAYMENT-SIGNATURE`. v1 replays omit asset context and need a previously observed, unambiguous challenge. v2 replays carry `accepted`. A status of 402 alone does not establish x402. |
19
- | Unbounded challenge observation (§4) | v1 correlation is local to each intake instance, with a five-minute TTL and at most 1,000 cached contexts. The key binds actual URL, method, task, agent, Authorization, and Cookie. Bodies are limited to 64 KiB; payment header text is limited to 65,536 characters. The proxy taps the streaming response without delaying delivery; the SDK additionally limits its cloned body observation to 100 ms. Missing, expired, oversized, slow, compressed proxy bodies, and ambiguous challenges cannot be metered reliably and pass through with a diagnostic. |
20
- | Token metadata in every event (§3) | Only Base and Base Sepolia USDC have trusted, static six-decimal metadata. Other assets retain exact amounts with `decimalsKnown: false` and an internal `decimals: 0` placeholder; displays say **atomic units**, not zero-decimal tokens. No RPC, token-list, or price lookup runs at runtime. |
21
- | One USDC balance and scalar maximum (§3) | Every total and budget is scoped separately by normalized network and contract. Symbol budgets match the local registry. `maxSingleAsset`, default `USDC`, scopes `maxSinglePayment`; unknown assets do not inherit a misleading one-USDC threshold. Optional budget `network` narrows a rule further. |
22
- | One `events` table and the original event fields (§3) | Keep immutable events and add append-only `outcomes` and `diagnostics` tables. Internal fields include `paymentKey`, `decimalsKnown`, `settlementStatus`, `settlement_unknown`, and derived `attemptedAt`; outcomes identify each forwarding attempt. These are Taximeter fields, not invented x402 wire fields. |
23
- | Deduplication by nonce/transaction/resource within a small window (§4) | A persistent key binds network, asset, payer, nonce, amount, recipient, and both authorization validity bounds. Signature, resource, and observation time are excluded. The same authorization counts once across retries and URLs; changed immutable authorization details retain a separate conservative reservation. |
24
- | Observed payments equal settled spend (§3, §4) | Reserve recognized authorizations synchronously in the same SQLite transaction as policy evaluation, before forwarding. Upstream-reported success is confirmation evidence, not independent settlement verification. Any confirmed attempt wins; otherwise any unresolved attempt retains capacity. Only an authorization whose known attempts all failed contributes zero. |
25
- | Retry timing and upstream failure (§4) | `attemptedAt` separates the latest unconfirmed forwarding attempt from immutable `ts`. An unconfirmed replay outside a rolling budget window must reacquire capacity. Missing settlement headers, disconnects, and unconfirmed 5xx responses remain unknown; an explicit matching success header can confirm even on a non-2xx response. |
26
- | Storage failure while preserving traffic (§4) | Runtime ledger/policy-intake storage failures fail open and emit a visible local warning plus a diagnostic when storage permits. A failed outcome write leaves its original reservation in place. Budget enforcement requires working storage; startup/configuration failures still fail visibly rather than claiming a working service. |
27
- | Universal zero-code proxy support (§2) | Clients must actually honor their proxy configuration. HTTPS CONNECT is an encrypted byte tunnel and is explicitly unmetered; Taximeter installs no certificate authority. An explicit localhost route with `--upstream`, or the SDK inside the payment wrapper, can inspect HTTP payment messages sent to HTTPS upstreams. |
28
- | Explicit base URL semantics (§2) | Origin-form requests require `upstream`. Its **origin** supplies the destination; the incoming path replaces any configured upstream path prefix. For example, upstream `https://api.example/v1` plus `/weather` forwards to `https://api.example/weather`. Leading `//` stays on the configured origin. Original encoded paths and query text are preserved separately from URL authority validation. |
29
- | Byte-identical forwarding and upgraded protocols (§4) | Preserve request/response payload bytes, duplicate end-to-end headers, and trailers, while rebuilding HTTP hop-by-hop headers and transfer framing as a proxy must. Recognized signed HTTP upgrade handshakes can be gated; subsequent upgraded stream frames are unmetered and diagnosed. CONNECT and upgrades do not imply inspection of payments hidden in their streams. |
30
- | Wrapping an arbitrary payment-enabled fetch (§2) | Compose `wrapFetchWithPayment(withMeter(fetch, options), client)` so each signed replay reaches the meter. The supplied transport's internal retries and redirects are invisible. A response marked `redirected` emits a visibility diagnostic and is never cached as a v1 challenge for the original URL. Host-sensitive callers can request `redirect: "error"` or expose each hop. The wrapper returns the original response and never consumes a request body. |
31
- | Configuration precedence (§3) | Extend precedence to flags > `TAXIMETER_*` environment > explicit `--config` file > cwd file > home file > defaults. The explicit file layers over automatic files. Environment support is exactly `TAXIMETER_DB`, `TAXIMETER_PORT`, and `TAXIMETER_DASHBOARD_PORT`. Relative database paths resolve from cwd; `~` expands locally. |
32
- | Additional configuration choices (§3) | `upstream` supports explicit routing; budget windows support `1h`, `24h`, `7d`, and `30d`; nullable budgets and `maxSinglePayment` disable their respective rules; port `0` requests an available port. Config and CLI options remain strict Zod inputs. Host/recipient allowlists are exact, case-insensitive matches, not wildcard patterns. |
33
- | Reset and local checks (§2) | `reset --yes` archives rather than deletes the ledger and acquires an atomic CLI lock. All SDK writers must be stopped separately; the lock coordinates CLI instances only. Read-only commands use an empty in-memory ledger when the path is absent. `doctor` checks local configuration and SQLite without requesting any upstream; it does not certify wallet/facilitator access or write permission for a nonexistent database path. |
34
- | Dashboard session and hero (§5) | Timeline shows the current UTC hour and preceding 23 hourly buckets, not an arbitrary process session. The Now hero shows the selected asset's active global budget window; all-time ledger totals are labeled separately. Assets and networks never share one monetary chart total. The latest 30 events and 10 diagnostics are shown; raw payment JSON remains available in JSON export. |
35
- | Thirty visible rows and Google Fonts (§5) | The event stream keeps 30 recent rows, with scrolling where screen height or width requires it. System grotesque and monospace stacks replace runtime Google Fonts requests to honor the network invariant. Complete light/dark tokens and exact BigInt-derived chart geometry are retained. |
36
- | Export and invoice format (§2, §5) | CSV includes all derived payment rows: `amount` is the counted contribution, while `authorizedAmount` preserves the original proposal. Blocked and wholly failed payments contribute zero. Sum each network/asset separately to reproduce the dashboard ledger totals. The invoice is standalone printable HTML, with no PDF renderer, exchange-rate conversion, tax calculation, or settlement attestation. |
37
- | Module and dependency choices (§7) | Ship ESM only, with no optional CommonJS build. Pin `better-sqlite3` 12.8.0 to preserve Node 20 support; version 13 requires Node 22. Exact pins and the audited esbuild override are recorded in `package.json` and the lockfile. |
38
- | Fixture realism and financial claims (§7) | Fixture envelopes follow primary protocol examples but all authorizations, signatures, and settlement responses are synthetic. Tests exercise parsing, transport, budget races, failures, and exact accounting; no real transfers, wallet signing, facilitator call, or independent chain verification is performed. |
39
- | Public installation and repository tagline (§1, §6) | The initial registry check returned `E404`; npm ownership was subsequently established with `0.1.0`. The release package uses `0.1.1`, and the README provides both `npx taximeter start` and source-build instructions. Fresh-directory startup from the local tarball is documented in `VERIFICATION.md`. Repository visibility is a maintainer-managed setting. The intended repository description is **A taximeter for your AI agents.** |
40
-
41
- These limitations make Taximeter a cooperative local meter. An agent that
42
- bypasses the intake, uses an unsupported payment method, or hides a replay
43
- inside a transport can bypass its budget checks. No supported flow holds funds,
44
- private keys, or payment-signing authority.
45
-
46
- ## Primary sources and published packages
47
-
48
- The requested [Coinbase repository README](https://github.com/coinbase/x402/blob/main/README.md)
49
- now identifies Coinbase's repository as a development fork and points to the
50
- [x402 Foundation repository](https://github.com/x402-foundation/x402) as the project
51
- home. The requested files were read, together with the Foundation specifications,
52
- the HTTP transport specification, documentation, and actual npm tarballs:
53
-
54
- - [Protocol v1](https://github.com/x402-foundation/x402/blob/main/specs/x402-specification-v1.md)
55
- and [protocol v2](https://github.com/x402-foundation/x402/blob/main/specs/x402-specification-v2.md).
56
- - [Requested Coinbase v2 specification](https://github.com/coinbase/x402/blob/main/specs/x402-specification-v2.md)
57
- and [exact EVM scheme](https://github.com/coinbase/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md).
58
- - [HTTP v2 transport](https://github.com/x402-foundation/x402/blob/main/specs/transports-v2/http.md).
59
- - [x402 documentation](https://docs.x402.org/introduction),
60
- [migration guide](https://docs.x402.org/guides/migration-v1-to-v2),
61
- [network and token support](https://docs.x402.org/core-concepts/network-and-token-support),
62
- [exact scheme](https://docs.x402.org/schemes/exact), and
63
- [payment identifier extension](https://docs.x402.org/extensions/payment-identifier).
64
-
65
- All four published packages resolved to **2.25.0**. Their tarballs were downloaded
66
- with `npm pack`, extracted outside the repository, and their READMEs, package
67
- exports, and relevant exported declarations were inspected. These are research
68
- inputs, not runtime dependencies of Taximeter.
69
-
70
- | Package | Published API inspected | Relevant finding |
71
- | --- | --- | --- |
72
- | [@x402/core 2.25.0](https://www.npmjs.com/package/@x402/core/v/2.25.0) | `types`, `types/v1`, `schemas`, `client`, `http`, `server` | Both protocol versions; header helpers; optional fields differ between nominal types and runtime schemas. |
73
- | [@x402/evm 2.25.0](https://www.npmjs.com/package/@x402/evm/v/2.25.0) | Main types, `exact/client`, `exact/server`, `exact/facilitator`, `v1` | EIP-3009 and Permit2 payloads; legacy network map; local default-asset metadata. |
74
- | [@x402/fetch 2.25.0](https://www.npmjs.com/package/@x402/fetch/v/2.25.0) | `wrapFetchWithPayment`, `wrapFetchWithPaymentFromConfig` | The supplied fetch is called for the initial request and each signed replay. |
75
- | [@x402/express 2.25.0](https://www.npmjs.com/package/@x402/express/v/2.25.0) | `paymentMiddleware`, `paymentMiddlewareFromConfig`, `paymentMiddlewareFromHTTPServer`, `ExpressAdapter` | Middleware verifies and settles; Taximeter must not adopt that facilitator role. |
76
-
77
- The pinned package declarations can also be inspected at
78
- [@x402/core types](https://unpkg.com/@x402/core@2.25.0/dist/esm/types/index.d.mts),
79
- [@x402/core v1 types](https://unpkg.com/@x402/core@2.25.0/dist/esm/types/v1/index.d.mts),
80
- [@x402/evm types](https://unpkg.com/@x402/evm@2.25.0/dist/esm/index.d.mts),
81
- [@x402/fetch types](https://unpkg.com/@x402/fetch@2.25.0/dist/esm/index.d.mts), and
82
- [@x402/express types](https://unpkg.com/@x402/express@2.25.0/dist/esm/index.d.mts).
83
-
84
- ## HTTP wire formats
85
-
86
- HTTP header names are case-insensitive. Payment headers contain Base64-encoded
87
- UTF-8 JSON, not the signature alone. A response is not necessarily x402 just
88
- because its status is 402.
89
-
90
- | Message | v1 | v2 |
91
- | --- | --- | --- |
92
- | Payment requirements | JSON response body with `x402Version: 1` | `PAYMENT-REQUIRED` response header with `x402Version: 2` |
93
- | Signed payment replay | `X-PAYMENT` request header | `PAYMENT-SIGNATURE` request header |
94
- | Settlement result | `X-PAYMENT-RESPONSE` response header | `PAYMENT-RESPONSE` response header |
95
-
96
- In v2, the response body is application content. It may be empty, JSON, HTML, or
97
- another media type. It is not the authoritative location of payment requirements.
98
- The published HTTP client checks `PAYMENT-REQUIRED` first and uses a v1 body only
99
- as the compatibility fallback. It recognizes both settlement header names.
100
- [HTTP transport source](https://github.com/x402-foundation/x402/blob/main/specs/transports-v2/http.md)
101
-
102
- ### v1 envelopes
103
-
104
- The following notation uses `?` for optional fields and describes the wire shape,
105
- not a TypeScript import from a payment SDK:
106
-
107
- ```text
108
- PaymentRequiredV1 {
109
- x402Version: 1,
110
- error?: string,
111
- accepts: PaymentRequirementsV1[]
112
- }
113
-
114
- PaymentRequirementsV1 {
115
- scheme: string,
116
- network: string,
117
- maxAmountRequired: string,
118
- resource: string,
119
- description: string,
120
- mimeType?: string,
121
- outputSchema?: object | null,
122
- payTo: string,
123
- maxTimeoutSeconds: number,
124
- asset: string,
125
- extra?: object | null
126
- }
127
-
128
- PaymentPayloadV1 {
129
- x402Version: 1,
130
- scheme: string,
131
- network: string,
132
- payload: object
133
- }
134
- ```
135
-
136
- The v1 specification's field table requires `error`; the published 2.25.0 Zod
137
- schema makes it optional. Its `mimeType`, `outputSchema`, and `extra` are also
138
- optional even though the nominal declaration is stricter. Taximeter follows the
139
- compatible runtime shape for these fields. `accepts` must contain at least one
140
- option; `maxTimeoutSeconds` is a positive number.
141
-
142
- Legacy networks use names such as `base` and `base-sepolia`. A v1 signed replay
143
- does **not** carry `asset`, `resource`, `accepted`, or `maxAmountRequired` at its
144
- top level. The preceding challenge supplies that missing context.
145
- [v1 source](https://github.com/x402-foundation/x402/blob/main/specs/x402-specification-v1.md)
146
-
147
- ### v2 envelopes
148
-
149
- ```text
150
- ResourceInfo {
151
- url: string,
152
- description?: string,
153
- mimeType?: string
154
- }
155
-
156
- PaymentRequirementsV2 {
157
- scheme: string,
158
- network: string,
159
- amount: string,
160
- asset: string,
161
- payTo: string,
162
- maxTimeoutSeconds: number,
163
- extra?: object | null
164
- }
165
-
166
- PaymentRequiredV2 {
167
- x402Version: 2,
168
- error?: string,
169
- resource: ResourceInfo,
170
- accepts: PaymentRequirementsV2[],
171
- extensions?: object | null
172
- }
173
-
174
- PaymentPayloadV2 {
175
- x402Version: 2,
176
- resource?: ResourceInfo,
177
- accepted: PaymentRequirementsV2,
178
- payload: object,
179
- extensions?: object | null
180
- }
181
- ```
182
-
183
- The v2 amount field is `amount`, not v1's `maxAmountRequired`. The selected option
184
- is `accepted`, not `accepts`. The required-response resource is a separate object;
185
- the replay's resource is optional. Networks use CAIP-2 identifiers such as
186
- `eip155:8453` and `eip155:84532`.
187
-
188
- Published schemas accept `null` for optional v2 resource/error metadata and
189
- normalize it to absence. They also permit resource metadata `serviceName`, `tags`,
190
- and `iconUrl`. These optional fields and unknown extensions do not determine
191
- spend. The original parsed JSON is retained for auditing, and forwarding does
192
- not reserialize or rewrite the payment payload.
193
- [v2 source](https://github.com/x402-foundation/x402/blob/main/specs/x402-specification-v2.md)
194
-
195
- ### Exact EVM EIP-3009 payload, both versions
196
-
197
- ```text
198
- payload {
199
- signature: hex-string,
200
- authorization: {
201
- from: EVM-address,
202
- to: EVM-address,
203
- value: unsigned-decimal-integer-string,
204
- validAfter: unsigned-decimal-integer-string,
205
- validBefore: unsigned-decimal-integer-string,
206
- nonce: 32-byte-hex-string
207
- }
208
- }
209
- ```
210
-
211
- `value` is the signed token amount. `validAfter` and `validBefore` are Unix seconds
212
- represented as strings; they are not the observation timestamp. `from` is the
213
- payer and `to` is the recipient. An exact payment's signed `value` must match the
214
- selected requirement's amount; its recipient must match `payTo`. Comparisons of
215
- EVM addresses are case-insensitive. Parsing these relationships is not signature
216
- verification.
217
-
218
- The scheme document describes a 65-byte EOA signature, while published
219
- `ExactEIP3009Payload` declares `signature?` and the implementation supports smart
220
- account signature paths. A monitor must not assume every supported signature is
221
- exactly 65 bytes. Unsupported or structurally unrecognized payloads remain
222
- transparent traffic, with a diagnostic.
223
- [Exact EVM source](https://github.com/coinbase/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md)
224
-
225
- The same exact-scheme source also describes Permit2
226
- (`payload.permit2Authorization` with `permitted.token`, `permitted.amount`, `from`,
227
- `spender`, decimal `nonce`, `deadline`, and `witness.to`/`witness.validAfter`) and
228
- ERC-7710 (`delegationManager`, `permissionContext`, `delegator`). Published EVM
229
- 2.25.0 types include EIP-3009 and Permit2. These are distinct payload formats, not
230
- aliases for `authorization`. Taximeter's initial adapter does not meter them.
231
-
232
- ### Settlement responses
233
-
234
- Both header generations decode to an object with the following fields. There is
235
- no required `x402Version` in this object:
236
-
237
- ```text
238
- SettleResponse {
239
- success: boolean,
240
- transaction: string,
241
- network: string,
242
- payer?: string,
243
- errorReason?: string,
244
- errorMessage?: string,
245
- amount?: unsigned-decimal-integer-string,
246
- extensions?: object,
247
- extensionResponses?: object,
248
- extra?: object
249
- }
250
- ```
251
-
252
- `transaction` is the wire field. Taximeter maps it to internal `txHash`; it does
253
- not search for an invented wire `txHash`. Failed settlement may use an empty
254
- transaction string. v1 uses legacy network names; v2 uses CAIP-2. Optional
255
- `amount` appears in current v2 types for schemes whose settlement differs from
256
- their authorization; it is not permission to treat `upto` as `exact`. Published
257
- types make `payer` optional in both versions, despite the older v1 table.
258
- [Settlement source](https://github.com/x402-foundation/x402/blob/main/specs/x402-specification-v2.md)
259
-
260
- The monitor considers settlement headers on all response statuses. A signed
261
- replay, HTTP 200, or transaction-looking string alone is not independently
262
- verified settlement. A recognized success is described as **reported settled**.
263
- Malformed, missing, or inconsistent settlement evidence remains unknown.
264
-
265
- ## Supported subset and accounting decisions
266
-
267
- 1. **Versions and networks.** Meter v1 and v2 HTTP `exact` EVM EIP-3009 payments.
268
- Normalize v1 `base` to `eip155:8453` and `base-sepolia` to `eip155:84532`.
269
- Other legacy names are outside the first implementation's mapping. v2 accepts
270
- EVM CAIP-2 networks. Other schemes, transfer methods, networks, and versions
271
- pass through with a diagnostic. This bounded subset supports the common USDC
272
- flow without pretending to understand different authorization semantics.
273
- 2. **Challenge correlation.** v1 requires an observed compatible challenge.
274
- Correlation uses actual URL, method, task, agent, Authorization, and Cookie,
275
- with a five-minute TTL and 1,000-context capacity. It then matches scheme,
276
- network, recipient, and atomic amount.
277
- Ambiguous asset choices must not be guessed. A v2 replay can supply its own
278
- selected requirements, so the initial 402 exchange need not have been seen.
279
- Host policy uses the actual upstream URL, not an untrusted advertised URL.
280
- The SDK does not cache a response marked `redirected` under the original URL.
281
- 3. **Asset identity.** Totals and budget windows are separate for every normalized
282
- network and asset contract. An asset symbol is a label, not an identity.
283
- Case-normalized EVM addresses avoid duplicate groups for checksum variants.
284
- 4. **Decimals.** Neither protocol envelope provides reliable decimals or ticker
285
- metadata. `extra.name` and `extra.version` are EIP-712 domain fields, not a
286
- verified symbol/decimals pair. Use a local registry for Base and Base Sepolia
287
- USDC only. Unknown assets retain exact atomic units, `decimalsKnown: false`,
288
- and an internal `decimals: 0` placeholder; the UI labels them as atomic units.
289
- It must not claim that the token has zero decimals. There are no runtime RPC
290
- or metadata lookups.
291
- 5. **Local USDC registry.** The six-decimal entries are Base
292
- `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` and Base Sepolia
293
- `0x036CbD53842c5426634e7929541eC2318f3dCF7e`, as documented in the
294
- [official asset table](https://docs.x402.org/core-concepts/network-and-token-support).
295
- An arbitrary contract advertising `name: "USDC"` does not join these groups.
296
- 6. **Budget asset scope.** The brief's scalar `maxSinglePayment` defaults to
297
- verified USDC, made explicit through `maxSingleAsset` with default `USDC`.
298
- Symbol budgets match trusted local registry entries and are evaluated
299
- separately for each network/asset pair. There is no cross-chain combined
300
- dollar balance and no exchange-rate conversion.
301
- 7. **Authorization and outcomes.** A challenge is an offer, not spend. A forwarded
302
- recognized authorization consumes conservative budget capacity before the
303
- asynchronous upstream request completes. Append-only outcome observations
304
- distinguish reported settlement, known failure, and unknown settlement.
305
- Outcomes retain separate attempt identities. Confirmation on any attempt
306
- wins; otherwise any unresolved attempt retains capacity. Only wholly failed
307
- attempts release a payment's contribution. Missing settlement evidence, a
308
- disconnect, or an unconfirmed upstream 5xx after forwarding does not silently
309
- release that capacity. `attemptedAt` permits an unconfirmed retry outside a
310
- rolling window to reacquire capacity without rewriting the original event.
311
- 8. **Idempotency.** The principal EIP-3009 identity includes network, asset,
312
- payer, and nonce, bound to amount, recipient, `validAfter`, and `validBefore`.
313
- Repeated observations of that authorization count once,
314
- including observations under a different resource. Nonce alone would collide
315
- across payers/contracts; transaction hash alone can collapse different
316
- transfers in one transaction. Resource and wall-clock time alone are not a
317
- reliable payment identity. The optional protocol payment-identifier extension
318
- is preserved but is not trusted to override an EIP-3009 authorization identity.
319
- Signature bytes are not part of the key. Conflicting immutable authorization
320
- details receive separate conservative reservations rather than a free replay.
321
- 9. **Concurrency.** Policy evaluation and reservation must be one synchronous
322
- operation before forwarding. Otherwise concurrent requests can all observe
323
- the same old total. Totals derive from the event log; settlement observations
324
- do not mutate earlier rows.
325
- 10. **Transparency boundary.** Unsupported or malformed traffic is forwarded
326
- with its payload unchanged and `parse_failed` diagnostics. Storage failures
327
- also fail open, with a visible local warning; enforcement needs working
328
- storage. Therefore this is a cooperative
329
- local meter, not a security boundary against an agent deliberately bypassing
330
- the proxy or using an unsupported payment form. Raw audit payloads are
331
- payment authorizations, not private keys; the ledger is local state.
332
-
333
- ## Additional protocol corrections relative to the brief
334
-
335
- The comprehensive implementation inventory is at the top of this file. These
336
- additional corrections concern the research premise itself:
337
-
338
- | Brief assumption or omission | Resolution |
339
- | --- | --- |
340
- | Coinbase repository is the primary project home. | Read the requested files and follow the README's Foundation pointer; preserve source links and research date above. |
341
- | Nominal TypeScript declarations are the only accepted wire shape. | Published 2.25.0 runtime schemas permit optional/nullish descriptive fields and optional EIP-3009 signatures. Follow compatible schemas without weakening the authorization relationships that determine spend. |
342
- | The payment rails have no budget support. | Published core 2.25.0 already has client `spendControls`, including a default per-payment cap. Taximeter's distinct contribution is the persistent local ledger, attribution, rolling budgets, and exports. |
343
-
344
- The HTTPS limitation follows from the transport design: a CONNECT tunnel gives
345
- the proxy an encrypted stream, while the payment protocol lives inside HTTP
346
- headers. Taximeter does not install a certificate authority or intercept TLS.
347
- The package README documents which connection modes are metered.
348
-
349
- For SDK composition, the intended pattern is
350
- `wrapFetchWithPayment(withMeter(fetch, options), client)`. The official fetch
351
- implementation invokes the supplied transport again for the replay, so this
352
- placement allows the meter to observe the challenge and gate the authorization
353
- before it reaches the upstream.
354
- [@x402/fetch source](https://unpkg.com/@x402/fetch@2.25.0/dist/esm/index.mjs)
355
-
356
- ## Fixture provenance and verification limits
357
-
358
- Fixture field names, example Base Sepolia token address, and envelope layout
359
- come from the linked v1/v2 and exact EVM specification examples. Test values,
360
- nonces, signatures, resource URLs, and transaction identifiers may be adapted
361
- deterministically for localhost tests. They are **synthetic protocol examples**,
362
- not captures of real paid requests and not evidence of cryptographic validity.
363
- The fixture server accepts the documented replay shape without moving money.
364
-
365
- Parser tests establish structural compatibility and exact accounting;
366
- integration tests establish forwarding, blocking, idempotency, and failure
367
- handling. Neither test category establishes on-chain settlement, facilitator
368
- availability, wallet compatibility, or universal x402 support. Those claims are
369
- outside the initial implementation and its non-custodial scope.