Parties, attachment links, statements, reconciliations (store), TIN vault crate
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
Parties: one directory for vendors, contractors, customers, employees, members, platforms, agencies, banks, landlords (ledger-core parties.rs). W-9/W-4 facts without the number (last four only; any nine-digit run in a party field is refused, Treas. Reg. 301.6109-4). Vendor and customer records link to parties on replay; core tags every bill, invoice, payment, and reversal with its counterparty. Journal events party.*, entry.party_set; payroll lines name the employee party. MCP: books.add_party, update_party, set_party_roles, set_w9, set_w4, parties, party, set_entry_party; draft_entry party; run_payroll employee; show party. Viewer: Parties directory and party pages, search, journal text, W-9 attention, demo data. One definition of a party's money (ledger_core::parties::activity) for MCP and viewer. 1099 amounts count payments from cash and financing accounts (a member paying for the company counts; a card doesn't; a prepayment counts when made): vendor_1099_report_with, B-19. Store: attachment links to any subject, statements, reconciliations (schema v11), with replay and projection. ledger-vault: encrypted TIN and W-9 records (ChaCha20-Poly1305, key file outside the books). Spec-blind tests for parties (core, store, MCP, viewer) and attachments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
This commit is contained in:
1 parent
0a62353767
commit
e265ed4339
54 files changed
+10961
-178
No files matched your search
+125
-14
@@ -111,8 +111,10 @@ below.
|
||||
|
||||
- `books.chart` → `{ accounts: [{ id, code, name, type, parent, placeholder,
|
||||
balance }] }`. Balances included: one call answers "what do we have."
|
||||
- `books.draft_entry { date: "2026-10-10", description: "..." }`
|
||||
→ `{ entry_id, status: "draft" }`.
|
||||
- `books.draft_entry { date: "2026-10-10", description: "...", vendor?,
|
||||
party? }` → `{ entry_id, status: "draft" }`. `party` tags the
|
||||
counterparty (see "Tools — parties"); given with `vendor`, both must name
|
||||
the same vendor record, else nothing is drafted.
|
||||
- `books.add_line { entry_id, account: "1000" | <id>, side: "debit" | "credit",
|
||||
amount: "123.45" }` → `{ ok: true }`. A refused line (unknown account or
|
||||
class, bad amount) adds nothing.
|
||||
@@ -123,7 +125,8 @@ below.
|
||||
the receipt policy is enforced and `status` is `"posted"`.
|
||||
- `books.reverse_entry { entry_id, date, description, confirmed }` → same
|
||||
preview/confirm pattern as post.
|
||||
- `books.get_entry { entry_id }` → full entry with lines.
|
||||
- `books.get_entry { entry_id }` → full entry with lines, receipts, and
|
||||
its `vendor`, `party_id`, and `party` (the party's label) tags.
|
||||
- `books.search_entries { account?, date_from?, date_to?, text?, limit?, offset? }`
|
||||
→ entries with lines, newest first. `limit` caps at 100.
|
||||
- `books.trial_balance { as_of? }` → `as_of`, lines, totals, `balanced`
|
||||
@@ -222,12 +225,13 @@ through the policy.
|
||||
|
||||
## Tools — the viewer
|
||||
|
||||
- `books.show { entry_id | account | report | attachment_id | search |
|
||||
page, start?, end? }` → `{ shown, note }`. Asks the human's viewer
|
||||
(`ledger-gui`) to open an entry, an account register or a report (for
|
||||
start/end dates if given), a stored file, a transactions search, or a
|
||||
page. Exactly one target; entries, accounts, and attachments are checked
|
||||
to exist. Never touches the books: the request is a small JSON file in
|
||||
- `books.show { entry_id | account | report | attachment_id | asset |
|
||||
party | search | page, start?, end? }` → `{ shown, note }`. Asks the
|
||||
human's viewer (`ledger-gui`) to open an entry, an account register or a
|
||||
report (for start/end dates if given), a stored file, an asset, a party
|
||||
(by id or name, resolved as in "Tools — parties"), a transactions search,
|
||||
or a page (`parties` among them). Exactly one target; entries, accounts,
|
||||
attachments, assets, and parties are checked to exist. Never touches the books: the request is a small JSON file in
|
||||
the user's runtime directory (`$XDG_RUNTIME_DIR/wanderbooks/viewer-<hash
|
||||
of the data dir>.json`), written atomically, watched by the viewer. A
|
||||
viewer started within two minutes of a request opens it; older requests
|
||||
@@ -269,19 +273,116 @@ overridable, always for the CPA to confirm. TINs are never stored, only
|
||||
whether a W-9 is on file.
|
||||
|
||||
- `books.list_vendors {}` → `{ vendors: [{ id, name, form_1099?,
|
||||
tin_on_file }] }`.
|
||||
tin_on_file, party_id }] }`.
|
||||
- `books.set_vendor_1099 { vendor, form_1099: "nec" | "misc" | "none",
|
||||
tin_on_file? }` → the vendor. `vendor` is an id or a name.
|
||||
- `books.set_entry_vendor { entry_id, vendor? }` → the entry (as
|
||||
`books.get_entry`, which now shows `vendor`). Omitting `vendor` clears
|
||||
the tag. Metadata only: posted entries may be tagged. `books.draft_entry`
|
||||
takes `vendor` too; bills and their payments are tagged automatically.
|
||||
`books.set_entry_party` tags the vendor too when the party has a vendor
|
||||
record, so tagging by party counts for 1099s the same way.
|
||||
- `books.vendor_1099 { year, threshold? }` → `{ year, threshold, vendors:
|
||||
[{ vendor_id, name, form_1099?, tin_on_file, paid, reportable }],
|
||||
missing_w9, notes }`. For a year whose threshold wanderbooks doesn't know
|
||||
(after 2026 until the indexed amount is added) it refuses with what to
|
||||
do; pass `threshold` explicitly once the figure is confirmed.
|
||||
|
||||
## Tools — parties
|
||||
|
||||
The directory of everyone the company deals with (`ledger_core::parties`;
|
||||
design in `docs/parties-and-attachments.md`): vendors, contractors,
|
||||
landlords, customers, payout platforms, tax agencies, banks and card
|
||||
issuers, members, employees. Vendor and customer *records* (bills,
|
||||
invoices, aging, 1099 marks) stay where they are; each links to exactly one
|
||||
party. `books.add_vendor` and `books.add_customer` link their record to the
|
||||
party of the same name (ignoring case and surrounding space, not archived)
|
||||
that has no such record yet, or make a new party.
|
||||
|
||||
**References.** A `party` argument is an id or a name: the name ignoring
|
||||
case and surrounding space, among parties not archived (an archived party
|
||||
goes by id). A number that is one party's id and another party's name is
|
||||
refused, never guessed.
|
||||
|
||||
**Roles:** `vendor`, `contractor`, `customer`, `employee`, `member`,
|
||||
`payout_platform`, `tax_agency`, `financial_institution`, `landlord`.
|
||||
Vendor and customer follow the records and are never set by hand.
|
||||
|
||||
**Rules the engine enforces** (each refusal changes nothing, the journal
|
||||
byte for byte): no party field holds a full TIN (a run of nine digits,
|
||||
single spaces or hyphens allowed between them); a W-9's TIN is its last
|
||||
four digits only; W-9 facts need a 1099 role (contractor or landlord), W-4
|
||||
facts the employee role; names are unique among parties not archived;
|
||||
texts are at most 500 characters, no control characters.
|
||||
|
||||
- `books.add_party { name, roles? }` → the party (below). Journaled as
|
||||
`party.added`.
|
||||
- `books.update_party { party, name?, display_name?, website?, address?,
|
||||
notes?, archived? }` → the party. Give at least one field; `""` clears
|
||||
`display_name`, `website`, `address`, or `notes`. Every field is checked
|
||||
(in order, on a rehearsal) before any is written; each is journaled as
|
||||
its own `party.updated`, in the order name, display_name, website,
|
||||
address, notes, archived. Un-archiving is refused while another party
|
||||
has the name.
|
||||
- `books.set_party_roles { party, add?, remove? }` → the party. Adds apply
|
||||
before removes (so a contractor's W-9 survives a swap to landlord); roles
|
||||
it already has, or lacks, are skipped; each change is journaled as
|
||||
`party.role_set`. Removing the last 1099 role with W-9 facts on file, or
|
||||
employee with W-4 facts, is refused: clear the facts first.
|
||||
- `books.set_w9 { party, received_on, entity_type, exempt_payee_code?,
|
||||
fatca_code?, tin_last4, kept_at }` or `{ party, clear: true }` → the
|
||||
party. Journaled as `party.w9_set`. A party with a vendor record also
|
||||
gets the record's `tin_on_file` set to match (journaled as
|
||||
`vendor.1099_set`), so `books.vendor_1099`'s `missing_w9` follows.
|
||||
- `books.set_w4 { party, received_on, state, kept_at }` or `{ party, clear:
|
||||
true }` → the party. Journaled as `party.w4_set`.
|
||||
- `books.set_entry_party { entry_id, party? }` → the entry (as
|
||||
`books.get_entry`). `party` null or omitted clears the tag. A party with
|
||||
a vendor record also tags the entry's vendor; one without, or clearing,
|
||||
clears the vendor tag. Metadata only; posted entries may be tagged.
|
||||
Journaled as `entry.party_set`.
|
||||
- `books.parties { role?, include_archived?, year? }` → `{ year, parties:
|
||||
[{ party_id, name, label, roles, vendor_id?, customer_id?, w9_on_file,
|
||||
tin_last4?, w4_on_file, archived, paid, received }] }`, by id.
|
||||
- `books.party { party, year? }` → `{ party, year, totals, entries,
|
||||
invoices, bills, payroll }`: the facts; the calendar year's totals
|
||||
(default this year); every entry it is on (drafts included, newest
|
||||
first); its invoices and bills (`{ id, date, due_date, total, paid,
|
||||
balance, status, entry_id }`); and the payroll lines naming it
|
||||
(`{ run_id, date, entry_id, gross_pay, net_pay }`).
|
||||
|
||||
The party (every mutating party tool returns it): `{ party_id, name,
|
||||
label, display_name?, roles, website?, address?, notes?, archived,
|
||||
vendor_id?, customer_id?, w9?: { received_on, entity_type,
|
||||
exempt_payee_code?, fatca_code?, tin_last4, kept_at }, w4?: { received_on,
|
||||
state, kept_at } }`. `label` is the display name, else the name; `roles`
|
||||
include vendor and customer when it has those records.
|
||||
|
||||
**Totals** (`books.party` `totals`; `paid` and `received` in
|
||||
`books.parties`), from the journal:
|
||||
|
||||
- *The party's entries:* tagged with the party; tagged with its vendor
|
||||
record (bills and their payments); its customer record's invoices and
|
||||
the payments received on them; and reversals of any of these. Only
|
||||
posted entries dated in the year count.
|
||||
- *Money accounts:* the `[books] cash_accounts` (every asset account but
|
||||
receivables when none are set), and every liability account except the
|
||||
payables and payroll withholding roles (`books.roles`). A liability
|
||||
counts as how the company paid or was paid: a card, a member paying for
|
||||
it, a loan.
|
||||
- `paid` / `received`: each entry's money lines, credits minus debits; a
|
||||
positive net was paid to the party, a negative one received from it; a
|
||||
reversal counts against its original's side. A bill isn't paid until its
|
||||
payment is; an entry only moving money between money accounts nets to
|
||||
zero.
|
||||
- `invoiced` / `billed`: its invoices' and bills' totals dated in the year.
|
||||
`owed` / `receivable`: its open bills' and invoices' balances now (every
|
||||
year).
|
||||
- `amount_1099` (vendor parties only): what `books.vendor_1099` counts as
|
||||
paid to its vendor record in the year (asset accounts only, so card
|
||||
payments drop out); `form_1099` is the record's mark.
|
||||
- `gross_pay` / `net_pay`: payroll lines naming it, in the year.
|
||||
|
||||
## Tools — receipts & documents
|
||||
|
||||
- `books.attach_receipt { entry_id?, file_name, mime_type, content_base64 }`
|
||||
@@ -457,6 +558,11 @@ in the server's tool descriptions, so the model reads it at use time):
|
||||
wrong posting is a reversal. Never guess-post.
|
||||
5. **The digest is the receipt for your work.** After a batch of bookkeeping,
|
||||
offer the digest; the human reviews, you fix.
|
||||
6. **Keep the party directory current.** Every counterparty gets a party;
|
||||
every entry is tagged with its party; contractors and landlords get W-9
|
||||
facts before they are paid, employees W-4 facts before their first
|
||||
payroll, and every payroll line names its employee party. Never a full
|
||||
TIN or SSN: the engine refuses nine-digit numbers.
|
||||
|
||||
## Out of scope for this phase
|
||||
|
||||
@@ -492,8 +598,9 @@ R-FUTURE-3 and rides the engine roadmap, not the MCP layer.
|
||||
|
||||
## Tools — A/R and A/P
|
||||
|
||||
- `books.add_customer { name }` → `{ id }`.
|
||||
- `books.add_vendor { name }` → `{ id }`.
|
||||
- `books.add_customer { name }` → `{ id, party_id }`.
|
||||
- `books.add_vendor { name }` → `{ id, party_id }`. Both link the record to
|
||||
a party ("Tools — parties").
|
||||
- `books.create_invoice { party_id, date, due_date, lines: [{ description,
|
||||
amount }] }` → `{ id, total, entry_id }`. Auto-posts Dr `1200` Accounts
|
||||
Receivable / Cr `4000` Revenue.
|
||||
@@ -522,13 +629,17 @@ R-FUTURE-3 and rides the engine roadmap, not the MCP layer.
|
||||
No tax tables: the caller supplies every number; the engine cuts correct
|
||||
entries.
|
||||
|
||||
- `books.run_payroll { date, cash_account, lines: [{ employee_name,
|
||||
gross_pay, federal_withholding, state_withholding, ss_employee,
|
||||
- `books.run_payroll { date, cash_account, lines: [{ employee?,
|
||||
employee_name?, gross_pay, federal_withholding, state_withholding, ss_employee,
|
||||
medicare_employee, ss_employer, medicare_employer, other_deductions }] }`
|
||||
→ `{ run_id, entry_id, total_gross, total_net }`. One balanced entry:
|
||||
Dr 6100 Salary & Wage Expense (gross), Dr 6110 Payroll Tax Expense
|
||||
(employer SS + Medicare), Cr 2110–2150 withholding payables, Cr cash
|
||||
(net). Auto-creates the 61xx/21xx accounts. Respects closed periods.
|
||||
`employee` names the employee's party (id or name; it must have the
|
||||
employee role, else nothing is written); new runs should name it, and
|
||||
`employee_name` then defaults to the party's name. A line needs one or
|
||||
the other.
|
||||
- `books.remit_payroll_taxes { date, cash_account }` → `{ entry_id, total }`.
|
||||
Clears every payroll payable to cash in one entry.
|
||||
- `books.list_payroll_runs` → `{ runs: [{ id, date, employee_count,
|
||||
|
||||
@@ -21,25 +21,30 @@ descriptions get records.
|
||||
|
||||
```text
|
||||
Party {
|
||||
id: PartyId, // vendors and customers keep their ids (see Migration)
|
||||
id: PartyId, // its own id space (vendor and customer records keep theirs)
|
||||
name: String, // legal or trade name, as on documents
|
||||
display_name: Option<String>,
|
||||
roles: BTreeSet<Role>, // vendor, contractor, customer, employee, member,
|
||||
// payout_platform, tax_agency, financial_institution, landlord
|
||||
roles: BTreeSet<Role>, // set by hand: contractor, employee, member, payout_platform,
|
||||
// tax_agency, financial_institution, landlord (vendor and
|
||||
// customer follow the records; see all_roles)
|
||||
website: Option<String>,
|
||||
address: Option<String>, // business address; no personal PII beyond it
|
||||
w9: Option<W9Facts>, // contractor/landlord: the TIN itself is in the vault
|
||||
w4: Option<W4Facts>, // employee: never the SSN
|
||||
form_1099: Option<Form1099>, // carried over from Vendor
|
||||
member: Option<String>, // a member's name in wanderbooks.toml, when role member
|
||||
notes: Option<String>,
|
||||
archived: bool, // no longer dealt with; history stays
|
||||
w9: Option<W9Facts>, // contractor/landlord: the TIN itself is in the vault
|
||||
w4: Option<W4Facts>, // employee: never the SSN
|
||||
vendor: Option<VendorId>, // its vendor record (bills, payments, 1099 form)
|
||||
customer: Option<CustomerId>,// its customer record (invoices)
|
||||
}
|
||||
W9Facts { received: NaiveDate, entity_type: String, exempt_payee_code: Option<String>,
|
||||
fatca_code: Option<String>, tin_last4: String, kept_at: String }
|
||||
fatca_code: Option<String>, tin_last4: String, kept_at: String,
|
||||
vault_record: Option<String> /* SHA-256 of the vault record */ }
|
||||
W4Facts { received: NaiveDate, state: String, kept_at: String }
|
||||
```
|
||||
|
||||
The 1099 form stays on the vendor record (`vendor.1099_set`); a party
|
||||
reaches it through `vendor`.
|
||||
|
||||
Rules (to vendor as B-26 with sources):
|
||||
|
||||
- A full TIN or SSN is never in the journal: `tin_last4` is exactly four
|
||||
@@ -65,29 +70,32 @@ Rules (to vendor as B-26 with sources):
|
||||
|
||||
### Journal events
|
||||
|
||||
`party.added {id, name, roles}`, `party.updated {id, field, value}` (one
|
||||
field per event: name, display_name, website, address, notes, archived),
|
||||
`party.role_added {id, role}`, `party.role_removed {id, role}`,
|
||||
`party.added {id, name, roles}`, `party.updated {id, change}` (one
|
||||
`PartyField` per event, `{field, value}`: name, display_name, website,
|
||||
address, notes, archived), `party.role_set {id, role, on}`,
|
||||
`party.w9_set {id, facts|null}`, `party.w4_set {id, facts|null}`,
|
||||
`entry.party_set`. Replayed and projected like the rest; every event gets
|
||||
viewer journal text (the GUI test enforces it).
|
||||
`entry.party_set {entry_id, party_id|null}`. Replayed and projected like
|
||||
the rest; every event gets viewer journal text (the GUI test enforces it).
|
||||
|
||||
### Migration
|
||||
|
||||
Old journals replay unchanged. `vendor.added` and `customer.added` keep
|
||||
their meaning and create parties with the vendor or customer role; the
|
||||
party id space starts above the highest vendor and customer ids, and a
|
||||
vendor and a customer with the same name stay two parties until the
|
||||
human merges them (`books.merge_parties`, journaled, re-pointing links).
|
||||
`entry.vendor_set` replays as `entry.party_set`. `vendor.1099_set` sets
|
||||
the party's `form_1099` and the contractor role.
|
||||
their meaning and, on replay as live, link their record to a party: the
|
||||
live party of that name (ignoring case and surrounding space) without
|
||||
such a record yet, else a new party. So a vendor and a customer of the
|
||||
same name share one party; a second vendor of a taken name gets a new
|
||||
party named "Name (2)" (names stay unique). Vendor and customer names
|
||||
obey the party text rules (no full TIN). `entry.vendor_set` also sets the
|
||||
entry's party, and `entry.party_set` to a party with a vendor record sets
|
||||
the vendor tag: one counterparty.
|
||||
|
||||
### Tools
|
||||
|
||||
`books.add_party`, `books.update_party`, `books.party_roles`,
|
||||
`books.set_w9`, `books.set_w4`, `books.parties` (list, filter by role),
|
||||
`books.party` (one: facts, roles, totals this year, documents, links),
|
||||
`books.merge_parties`, `books.set_entry_party`. `books.add_vendor` and
|
||||
`books.set_entry_party`. (Merging two parties, `books.merge_parties`, is
|
||||
not built yet: records of one name already share a party.) `books.add_vendor` and
|
||||
`books.add_customer` stay as shortcuts.
|
||||
|
||||
### Viewer
|
||||
@@ -111,10 +119,13 @@ worksheet it hands the CPA is complete, but never in the journal.
|
||||
W-9 file itself, each encrypted at rest (XChaCha20-Poly1305, one random
|
||||
nonce per record; the file format carries a version and the party id as
|
||||
associated data, so a record can't be moved to another party).
|
||||
- **Key:** a random 256-bit key in the OS keyring (Secret Service on Linux,
|
||||
Keychain on macOS), created on first use; never on disk in the books dir.
|
||||
A recovery copy is the human's job: `just vault-export` writes the key
|
||||
and records as one passphrase-encrypted file for a password manager.
|
||||
- **Key:** a random 256-bit key in a key file outside the books dir
|
||||
(default `~/.config/wanderbooks/vault.key`, mode 0600; `[vault] key_file`
|
||||
in wanderbooks.toml overrides it), created on first use. No OS keyring
|
||||
or other system service (Stephen, 2026-10-15: no external dependencies),
|
||||
so the vault works anywhere wanderbooks runs. Recovery is a copy of that
|
||||
file in the human's password manager; without it the vault can't be
|
||||
read (the books still open and verify).
|
||||
- **Journal:** `party.w9_set` records that a W-9 was received, its date,
|
||||
entity type, codes, and last four, plus a hash of the vault record, so
|
||||
the books prove a W-9 existed without holding it.
|
||||
|
||||
@@ -378,6 +378,15 @@ years after 2026, indexed to inflation and rounded to the nearest $100
|
||||
(§6041(h)). Card payments are reported on Form 1099-K, not 1099-NEC/MISC.
|
||||
Attorneys' gross proceeds stay at $600 (not modeled).
|
||||
|
||||
What counts as paid: payments made in the calendar year (§6041(a)), so a
|
||||
prepayment counts when made. The engine counts credits minus debits to the
|
||||
payment sources on a vendor's tagged entries: `[books] cash_accounts` and
|
||||
`financing_accounts` (a member or lender paying on the company's behalf;
|
||||
the company is still the payer). A card charge leaves neither (1099-K); a
|
||||
bill is not paid until it is (`form1099::vendor_1099_report_with`,
|
||||
checked by `payments_come_from_cash_and_financing_only`). A W-9 is on
|
||||
file by the vendor's flag or its party's W-9 facts.
|
||||
|
||||
**Enforced:** `form1099::default_threshold`, a dated rule
|
||||
(`ledger_core::rules::THRESHOLD_1099`, known through 2026, review from
|
||||
2026-11-01). For a later year the engine refuses with `RuleNotKnown`
|
||||
@@ -494,3 +503,26 @@ non-negative, in cents), `Asset::tax_basis`, `Asset::built_in_gain`,
|
||||
`missing_tax_basis`). **Checked:** `ledger-core` `tests/spec_tax_basis.rs`;
|
||||
`ledger-store` `tests/asset_tags.rs` (`asset.tax_basis_set` journaling,
|
||||
replay, v9 migration, corrupt payloads); `ledger-mcp` `tests/spec_assets.rs`.
|
||||
|
||||
## B-26 Parties: one directory, no full TIN in the books
|
||||
|
||||
**Status:** AUTHORITY (truncated TINs: `sources/irs-truncated-tin.md`);
|
||||
DECISIONS (Stephen, 2026-10-15)
|
||||
|
||||
Everyone the company deals with is a party, with roles (vendor,
|
||||
contractor, customer, employee, member, payout platform, tax agency,
|
||||
financial institution, landlord). Vendor and customer records link to
|
||||
their party; entries are tagged with theirs. Names are unique among parties
|
||||
not archived, ignoring case and surrounding space. A W-9 is recorded as its
|
||||
facts and the TIN's last four digits (the part a truncated TIN shows,
|
||||
Treas. Reg. §301.6109-4(a)); no party field may hold a nine-digit number,
|
||||
and the full TIN lives only in the encrypted vault, outside the journal
|
||||
(DECISION). W-9 facts need a 1099 role (contractor, landlord); W-4 facts
|
||||
need the employee role; payroll lines that name their employee name a
|
||||
party with that role.
|
||||
|
||||
**Enforced:** `crate::parties` (has_full_tin, check_text, W9Facts::check,
|
||||
W4Facts::check), `Ledger::add_party`, `update_party`, `set_party_role`,
|
||||
`set_party_w9`, `set_party_w4`, `set_entry_party`, `validate_payroll`.
|
||||
**Checked:** `ledger-core` `tests/spec_parties.rs`; `ledger-store`
|
||||
`tests/parties.rs`; `ledger-mcp` `tests/spec_parties.rs`.
|
||||
@@ -0,0 +1,16 @@
|
||||
# TIN formats
|
||||
Governs `ledger_vault::Tin::new`: a TIN is nine digits; an ITIN begins with 9 and its fourth and fifth digits fall in the ranges below; an SSN or EIN doesn't begin with 9 (a nine-digit number beginning with 9 is in the ITIN series).
|
||||
|
||||
## IRS Publication 1915 (Rev. 11-2024), "Understanding Your IRS Individual Taxpayer Identification Number", General Information
|
||||
Source: <https://www.irs.gov/pub/irs-pdf/p1915.pdf>
|
||||
Retrieved: 2026-10-15
|
||||
SHA-256 of the retrieved file (`p1915.pdf`): `63a3cf3e79c2e73ed15738d5a0875ffb7496c2309e8c3e085836ade16b2e0920`
|
||||
|
||||
> An ITIN is a tax processing number, issued by the Internal Revenue Service, for certain resident and nonresident aliens, their spouses, and their dependents. It is a nine-digit number beginning with the number “9”, has a range of numbers from "50" to "65", "70" to "88", “90” to “92” and “94” to “99” for the fourth and fifth digits and is formatted like a SSN (i.e. 9XX-7X-XXXX).
|
||||
|
||||
## Instructions for Form W-7 (Rev. December 2024), What's an ITIN
|
||||
Source: <https://www.irs.gov/instructions/iw7>
|
||||
Retrieved: 2026-10-15
|
||||
SHA-256 of the retrieved file (`irs-iw7.html`): `1d674a26a63c34b59a2cb6e34d050a95f744c53ae35ea50172a137298b37a000`
|
||||
|
||||
> An ITIN is a nine-digit number issued by the IRS to individuals who are required for federal tax purposes to have a U.S. taxpayer identification number but who don’t have and aren’t eligible to get a social security number (SSN).
|
||||
@@ -0,0 +1,11 @@
|
||||
# Truncated taxpayer identification numbers
|
||||
Governs what of a TIN the party directory may hold (crates/ledger-core/src/parties.rs): the last four digits, the same part a truncated TIN shows. The full number lives only in the encrypted vault (docs/parties-and-attachments.md), never in the journal.
|
||||
|
||||
## Treas. Reg. §301.6109-4(a) and (b)(1)
|
||||
Source: <https://www.ecfr.gov/current/title-26/part-301/section-301.6109-4>
|
||||
Retrieved: 2026-10-15
|
||||
SHA-256 of the retrieved file (`ecfr301-6109-4.html`): `45702c366f7f185e41452a32acf685bb5371e00b91bfb6214174ffd153eba949`
|
||||
|
||||
> (a) In general — Definition. An IRS truncated taxpayer identification number (TTIN) is an individual's social security number (SSN), IRS individual taxpayer identification number (ITIN), IRS adoption taxpayer identification number (ATIN), or IRS employer identification number (EIN) in which the first five digits of the nine-digit number are replaced with Xs or asterisks. The TTIN takes the same format of the identifying number it replaces, for example XXX-XX-1234 when replacing an SSN, or XX-XXX1234 when replacing an EIN.
|
||||
|
||||
> (b) Use of a TTIN. (1) In general. Except as provided in paragraph (b)(2) of this section, a TTIN may be used to identify any person on any statement or other document that the internal revenue laws require to be furnished to another person. Use of a TTIN is permissive and not mandatory.
|
||||
Reference in new issue
Block a user