Skip to content

Commit 554a84e

Browse files
thedavidwengclaude
andcommitted
feat: unify JSON envelope with the fleet core schema
Breaking JSON contract change, schema_version 2026-07-25 (ADR-0003): the top-level errors array becomes a single error object (extras in error.details), warnings move under meta, and meta gains profile, duration_ms, and request_id while dropping generated_at. Adds a global --pretty flag. Secret references migrate from the {env: NAME} map form to the fleet-wide env:NAME string (setup wizard and docs updated; no back-compat shim). All gocritic exclusions removed with the ~190 findings fixed in code, so .golangci.yml is canonical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent c1f355e commit 554a84e

81 files changed

Lines changed: 872 additions & 817 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.golangci.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,6 @@ linters:
2424
- diagnostic
2525
- performance
2626
- style
27-
disabled-checks:
28-
- hugeParam
29-
- rangeValCopy
30-
- paramTypeCombine
31-
- nestingReduce
32-
- unnamedResult
3327
misspell:
3428
locale: US
3529
nolintlint:

JSON_SCHEMA.md

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# JSON Envelope Schema
22

33
All commands emit a standard JSON envelope when invoked with `--json`.
4-
The current schema version is `0.1`.
4+
The current schema version is `2026-07-25`.
5+
6+
Output is compact by default; pass the global `--pretty` flag for indented JSON.
57

68
## Success Envelope
79

@@ -11,60 +13,61 @@ The current schema version is `0.1`.
1113
"data": { ... },
1214
"meta": {
1315
"command": "transactions.list",
14-
"schema_version": "0.1",
15-
"generated_at": "2026-05-10T00:00:00Z",
16+
"profile": "default",
17+
"duration_ms": 12,
18+
"schema_version": "2026-07-25",
19+
"request_id": "3f8a1c2e-9b4d-4e6a-8f21-0c5d7e2a1b9c",
1620
"pagination": {
1721
"limit": 50,
1822
"offset": 0,
1923
"has_more": false
2024
}
21-
},
22-
"warnings": [],
23-
"errors": []
25+
}
2426
}
2527
```
2628

2729
### Fields
2830

2931
- `ok` — always `true` on success.
3032
- `data` — command-specific payload. Collections are object-wrapped: `data.accounts`, `data.transactions`, `data.categories`, `data.tags`, `data.recurring`.
33+
- `error` — omitted on success (see below).
3134
- `meta` — request metadata (always present).
3235
- `meta.command` — dot-separated command name.
33-
- `meta.schema_version` — semver string.
34-
- `meta.generated_at` — ISO 8601 timestamp.
35-
- `meta.demo``true` when running in demo mode.
36+
- `meta.profile` — active configuration profile (`default` unless overridden).
37+
- `meta.duration_ms` — wall-clock time spent handling the invocation, in milliseconds.
38+
- `meta.schema_version` — envelope schema version as a date string.
39+
- `meta.request_id` — UUID v4 generated once per invocation.
40+
- `meta.demo``true` when running in demo mode (omitted otherwise).
3641
- `meta.pagination` — present on list commands (`limit`, `offset`, `has_more`, `total` when available).
37-
- `warnings` — array of structured warning objects with `code`, `message`, `category`.
38-
- `errors` — empty array on success.
42+
- `meta.warnings` — array of structured warning objects (`code`, `message`, `category`); omitted when empty.
3943

4044
## Error Envelope
4145

4246
```json
4347
{
4448
"ok": false,
4549
"data": { ... }, // omitted when empty (omitempty)
50+
"error": {
51+
"code": "SYNC_PARTIAL_FAILURE",
52+
"message": "One or more provider items failed to sync",
53+
"category": "api",
54+
"retryable": true
55+
},
4656
"meta": {
4757
"command": "sync",
48-
"schema_version": "0.1",
49-
"generated_at": "2026-05-10T00:00:00Z"
50-
},
51-
"warnings": [],
52-
"errors": [
53-
{
54-
"code": "SYNC_PARTIAL_FAILURE",
55-
"message": "One or more provider items failed to sync",
56-
"category": "api",
57-
"retryable": true
58-
}
59-
]
58+
"profile": "default",
59+
"duration_ms": 240,
60+
"schema_version": "2026-07-25",
61+
"request_id": "b1e6c7d4-2a3f-4c8b-9d10-6f2e5a7c3d81"
62+
}
6063
}
6164
```
6265

6366
### Error Fields
6467

6568
- `ok` — always `false` on error.
66-
- `errors`array of error objects (supports multi-error envelopes for partial failures).
67-
- Each error: `code`, `message`, `category`, `retryable`.
69+
- `error`a single structured error object. When several errors are aggregated (e.g. multi-item partial failures), the primary error is the object and the remaining errors are carried in `error.details`.
70+
- Each error: `code`, `message`, `category`, `retryable`, and optional `details` (an array of the same shape).
6871

6972
## Error Taxonomy
7073

@@ -113,6 +116,9 @@ Provider errors are classified as:
113116

114117
## Schema Versioning
115118

116-
- **Major** — breaking contract changes.
117-
- **Minor** — additive compatible fields.
118-
- **Patch** — implementation-only changes (no schema change).
119+
`schema_version` is a date string. It bumps to the date of the change on any
120+
breaking change to the envelope shape; additive, backward-compatible fields do
121+
not bump it. The `2026-07-25` version unified the envelope with the CLI fleet:
122+
a single `error` object replaced the previous `errors[]` array, warnings moved
123+
under `meta.warnings`, `meta` gained `profile`, `duration_ms`, and `request_id`,
124+
and the redundant `meta.generated_at` was dropped.

docs/ARCHITECTURE.md

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -212,23 +212,18 @@ After setup finishes writing configuration, it should create or open the encrypt
212212
```yaml
213213
database:
214214
path: ~/.money/data/money.db
215-
encryption_key:
216-
env: MONEY_DB_ENCRYPTION_KEY
215+
encryption_key: "env:MONEY_DB_ENCRYPTION_KEY"
217216

218217
providers:
219218
plaid:
220-
client_id:
221-
env: PLAID_CLIENT_ID
222-
secret:
223-
env: PLAID_SECRET
219+
client_id: "env:PLAID_CLIENT_ID"
220+
secret: "env:PLAID_SECRET"
224221
environment: sandbox
225222
products: [transactions]
226223
country_codes: [US]
227224
bridge:
228-
client_id:
229-
env: BRIDGE_CLIENT_ID
230-
client_secret:
231-
env: BRIDGE_CLIENT_SECRET
225+
client_id: "env:BRIDGE_CLIENT_ID"
226+
client_secret: "env:BRIDGE_CLIENT_SECRET"
232227
```
233228
234229
## Implementation Tooling

docs/CONFIG.md

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,27 @@
44

55
## Config Shape
66

7-
Secret references use YAML objects with exactly one `env` key:
7+
Secret references use the string form `env:NAME`, which resolves to the value of environment variable `NAME` at load time (an error is raised if it is unset):
88

99
```yaml
1010
database:
1111
path: ~/.money/data/money.db
12-
encryption_key:
13-
env: MONEY_DB_ENCRYPTION_KEY
12+
encryption_key: "env:MONEY_DB_ENCRYPTION_KEY"
1413

1514
providers:
1615
plaid:
17-
client_id:
18-
env: PLAID_CLIENT_ID
19-
secret:
20-
env: PLAID_SECRET
16+
client_id: "env:PLAID_CLIENT_ID"
17+
secret: "env:PLAID_SECRET"
2118
environment: sandbox
2219
products: [transactions]
2320
country_codes: [US]
2421
additional_consented_products: [investments]
2522
required_if_supported_products: [liabilities]
2623
optional_products: [auth]
27-
redirect_uri:
28-
env: PLAID_REDIRECT_URI
24+
redirect_uri: "env:PLAID_REDIRECT_URI"
2925
bridge:
30-
client_id:
31-
env: BRIDGE_CLIENT_ID
32-
client_secret:
33-
env: BRIDGE_CLIENT_SECRET
26+
client_id: "env:BRIDGE_CLIENT_ID"
27+
client_secret: "env:BRIDGE_CLIENT_SECRET"
3428
```
3529
3630
Direct scalar values are allowed only for non-secrets such as `database.path`, `providers.plaid.environment`, `products`, `country_codes`, and Plaid Link consent product lists. Direct scalar secrets are accepted for manually edited files, but config loading emits a structured warning recommending `.env` references.
@@ -61,7 +55,7 @@ Profile names must be alphanumeric, hyphen, or underscore only; path traversal c
6155
7. Validate required fields for the command being executed.
6256
8. Return config values plus structured warnings, such as direct secrets in YAML or broad env-file permissions.
6357

64-
Environment variables complete explicit references; they do not form a magic override chain. For example, `PLAID_SECRET` is used only when config says `secret: { env: PLAID_SECRET }` or a setup/configure command writes that reference.
58+
Environment variables complete explicit references; they do not form a magic override chain. For example, `PLAID_SECRET` is used only when config says `secret: "env:PLAID_SECRET"` or a setup/configure command writes that reference.
6559
6660
Plaid Dashboard OAuth bootstrap state is stored outside YAML at `plaid-dashboard-auth.json` beside the resolved config file. It is local bootstrap state for `money plaid login`, written `0600`, and may include Dashboard access/refresh tokens plus selected `team_id` and `client_id`. Provider API credentials still use the normal `.env` plus YAML `env:` references model.
6761

docs/CONTRACTS.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Command Contracts
22

3-
`money` commands write one JSON envelope to stdout when `--json` is set. Human mode may print compact text, but automation should use JSON.
3+
`money` commands write one JSON envelope to stdout when `--json` is set. Human mode may print compact text, but automation should use JSON. JSON output is compact by default; pass `--pretty` for indented output.
44

55
## Envelope
66

@@ -12,21 +12,20 @@ All JSON commands use:
1212
"data": {},
1313
"meta": {
1414
"command": "transactions.list",
15-
"schema_version": "0.1",
16-
"generated_at": "2026-05-10T00:00:00Z",
17-
"demo": false,
15+
"profile": "default",
16+
"duration_ms": 12,
17+
"schema_version": "2026-07-25",
18+
"request_id": "3f8a1c2e-9b4d-4e6a-8f21-0c5d7e2a1b9c",
1819
"pagination": {
1920
"limit": 50,
2021
"offset": 0,
2122
"has_more": false
2223
}
23-
},
24-
"warnings": [],
25-
"errors": []
24+
}
2625
}
2726
```
2827

29-
Errors use `ok: false` and `errors[]` entries with `code`, `message`, `category`, and `retryable`. JSON mode does not require stderr parsing.
28+
Errors use `ok: false` and a single `error` object with `code`, `message`, `category`, and `retryable` (plus optional `details[]` when errors are aggregated). Warnings, when present, are carried under `meta.warnings`. JSON mode does not require stderr parsing.
3029

3130
## Read Commands
3231

@@ -133,6 +132,7 @@ All commands support:
133132
- `--config` (config file path)
134133
- `--profile` (configuration profile, default "default")
135134
- `-j, --json` (write JSON envelope to stdout)
135+
- `--pretty` (indent JSON output)
136136

137137
The `--profile` flag selects a named configuration profile. The default profile uses `~/.money/config.yaml`; custom profiles use `~/.money/profiles/<name>/config.yaml`.
138138

@@ -308,4 +308,4 @@ These commands never include Plaid API secrets, Dashboard OAuth tokens, masked s
308308

309309
## Monarch Compatibility Notes
310310

311-
The command names and stdout/stderr discipline follow Monarch CLI habits where useful. `money` differs by using object-wrapped collection fields, multi-error envelopes, explicit source provenance, encrypted local storage, and BYOK Provider adapters.
311+
The command names and stdout/stderr discipline follow Monarch CLI habits where useful. `money` differs by using object-wrapped collection fields, a single structured `error` object (with aggregated errors in `error.details`), explicit source provenance, encrypted local storage, and BYOK Provider adapters.

docs/adr/0002-json-envelope-and-exit-codes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# JSON envelope and exit-code contract
22

3-
Status: Accepted
3+
Status: Accepted (envelope shape superseded by ADR-0003; exit-code contract still current)
44

55
## Context
66

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Fleet envelope core
2+
3+
Status: Accepted (amends ADR-0002)
4+
5+
## Context
6+
7+
`money` is one of five sibling CLIs (canvas, zenodo, flickr, monarch, money) that
8+
all emit a machine-readable JSON envelope. Round 1 aligned the tools on a shared
9+
core, but `money` still carried three shapes the rest of the fleet did not: a
10+
top-level `errors[]` array, a top-level `warnings[]` array, and a `meta.generated_at`
11+
timestamp. Divergent envelopes force agents and scripts to special-case each tool,
12+
which is the opposite of what a fleet-wide contract is for.
13+
14+
ADR-0002 froze the previous shape (`{ ok, data, meta, warnings, errors }` with an
15+
errors array "never a single object" and `meta` carrying `command`, `schema_version`,
16+
`generated_at`). That decision predates the fleet-unification effort and its
17+
envelope clauses are superseded here.
18+
19+
## Decision
20+
21+
Adopt the fleet core envelope. This is a breaking change; `schema_version` becomes
22+
the date string `2026-07-25`.
23+
24+
- `errors[]` collapses to a single `error` object `{ code, message, category, retryable, details? }`. When multiple errors are aggregated (e.g. multi-item partial failures) the primary error is the object and the remainder go into `error.details`. `error` is omitted on success.
25+
- Top-level `warnings[]` moves to `meta.warnings` (omitted when empty).
26+
- `meta` gains `profile` (the active configuration profile), `duration_ms` (wall-clock handling time), and `request_id` (a UUID v4 generated once per invocation).
27+
- `meta.generated_at` is dropped; it was redundant with request-scoped timing and non-deterministic in golden tests.
28+
29+
Tool-specific additive `meta` fields (`demo`, `pagination`) are retained. A single
30+
`runtimeState.writeEnvelope` chokepoint stamps the request-scoped fields and honors
31+
the global `--pretty` flag, so envelope bytes cannot drift between commands.
32+
33+
## Consequences
34+
35+
- Consumers that read `errors[0]` must read `error`; consumers that read top-level `warnings` must read `meta.warnings`. There is no compatibility shim.
36+
- `request_id` gives each invocation a stable correlation id for logs and audits; `duration_ms` exposes handling latency without a wall-clock timestamp.
37+
- `JSON_SCHEMA.md`, `docs/CONTRACTS.md`, and every inline/e2e envelope assertion were updated to the new shape.
38+
- `github.com/google/uuid` is a direct dependency, matching the sibling tools.

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.26.4
44

55
require (
66
charm.land/huh/v2 v2.0.3
7+
github.com/google/uuid v1.6.0
78
github.com/ncruces/go-sqlite3 v0.34.1
89
github.com/olekukonko/tablewriter v0.0.5
910
github.com/plaid/plaid-go/v40 v40.1.0

0 commit comments

Comments
 (0)