From 1802450e437de9d31b9044089c162993866f023d Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Thu, 2 Jul 2026 09:07:10 +0200 Subject: docs: spec for tx add --from-id/--to-id (ISSUES.md #2) Co-Authored-By: Claude Opus 4.8 --- .../specs/2026-07-02-from-id-to-id-design.md | 87 ++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-02-from-id-to-id-design.md (limited to 'docs/superpowers/specs') diff --git a/docs/superpowers/specs/2026-07-02-from-id-to-id-design.md b/docs/superpowers/specs/2026-07-02-from-id-to-id-design.md new file mode 100644 index 0000000..59f54b2 --- /dev/null +++ b/docs/superpowers/specs/2026-07-02-from-id-to-id-design.md @@ -0,0 +1,87 @@ +# `--from-id`/`--to-id` for `tx add` + +Date: 2026-07-02 +Resolves: ISSUES.md #2 (same-name accounts unresolvable via CLI) + +## Problem + +Two accounts share the name "Nexi": expense (id 52, the 15th debt-payment +target) and revenue (id 129, the 1st-of-month plafond-refill source). +`firefly tx add --from/--to "Nexi"` fails with +`Ambiguous account "Nexi" matches ids 52, 129`. The CLI accepts names only; a +numeric id is treated as a literal name and also fails. The intended +two-same-name-account cycle cannot be driven by the CLI. + +## Goal + +Add an id escape hatch to `tx add` so an ambiguous (or any) account can be +targeted by numeric id, bypassing name resolution. + +## Design + +### New resolver method (`resolver.py`) + +```python +def account_by_id(self, acc_id): + resp = self.client.request("GET", f"/api/v1/accounts/{acc_id}") + item = resp["data"] + return {"id": item["id"], **item["attributes"]} +``` + +GET a single account by id. Returns the same dict shape as `account(name)` +(`{"id", "name", "type", ...}`) so all downstream code is path-agnostic. A +bad id yields a 404 that `client.request` already surfaces as a +`FireflyError` — this is the existence validation (per the "validate id" +decision), no extra list scan needed. + +### Flags (`transaction.py` `_add_args`) + +- Add `--from-id` (dest `source_id`) and `--to-id` (dest `dest_id`). +- Drop `required=True` from `--from`/`--to`; requirement is now one-of-two + per side, enforced in the handler (argparse can't express XOR cleanly). + +### Handler validation (`cmd_add`) + +Per side, exactly one of the name/id pair must be supplied: + +- source: exactly one of `args.source`, `args.source_id` +- dest: exactly one of `args.dest`, `args.dest_id` + +Zero or both on a side → `FireflyError` with a clear message. Sides are +independent: `--from NAME --to-id 129` is valid (name on one side, id on the +other). + +### Resolution + +```python +src = (ctx.resolver.account_by_id(args.source_id) if args.source_id + else ctx.resolver.account(args.source)) +dst = (ctx.resolver.account_by_id(args.dest_id) if args.dest_id + else ctx.resolver.account(args.dest)) +``` + +Everything downstream is unchanged: type inference, the transfer-direction +stderr echo, the skip-dupes search query, and the emitted split all use +`src`/`dst` dicts identically for both paths. + +## Contract impact → PATCH (v0.3.7) + +New optional flags. `--from`/`--to` are no longer argparse-required, but the +handler still requires one per side, so existing name-only callers are +unaffected. JSON output shape and exit codes are unchanged. Per the +contract-keyed scheme this is a PATCH. + +## Tests (mocked) + +- id path resolves via `account_by_id` and writes the split with that id +- missing both name and id on a side → error +- both name and id on the same side → error +- mixed: name on source, id on dest → works +- a 404 from `account_by_id` surfaces as a FireflyError + +## Out of scope (YAGNI) + +- id support on `tx list` or other name-taking commands — add when a real + blocker appears; `tx list` was not reported as blocked. +- `--from-type`/type-scoped resolution — the id flags cover disambiguation. +- ISSUES.md #1 (liability account creation) — separate, larger, next session. -- cgit v1.2.3