import Big from 'big.js'; declare class Currency { readonly code: string; readonly scale: number; private constructor(); /** * `scaleOverride` sets the decimal-places scale explicitly (pack parameter * `packPolicy.currencyScale`) — overrides the global default/ISO scale per * tenant (jurisdiction-free substrate: scale is a pack matter, not global). */ static of(code: string, scaleOverride?: number): Currency; equals(other: Currency): boolean; toJSON(): string; toString(): string; } /** * Amount = decimal value + currency, never float (Glossary `money`). * * Determinism rules (determinismus.md §2): * - rounding: commercial half-up, away from zero at exactly .5. * - allocate: largest-remainder, tie → first part; Σ parts = amount, always. * * The amount internally always lies exactly on the currency scale. */ declare class Money { private readonly amount; readonly currency: Currency; private constructor(); private static currencyOf; /** * Exact amount on the currency scale. More decimal places than the currency * allows is an error — nothing is ever silently rounded here. * * The string must match the **data format** (`format.schema.json` * `$defs/money/properties/amount`), not merely be something the decimal library * can parse. The library is more permissive than the format: it read `"1e3"` as * 1000.00 and `"1.5e+21"` as a booking of 1.5 sextillion, and it disagreed * across languages on a leading `+`. An amount that would not survive a * round-trip through the exported format has no business entering the journal. */ static of(amount: string, currency: Currency | string): Money; /** * Bring the result of a calculation to the currency scale: half-up * (2.225 → 2.23, -2.345 → -2.35). The only path on which Money rounds. */ static fromCalculation(value: Big | string, currency: Currency | string): Money; static zero(currency: Currency | string): Money; add(other: Money): Money; subtract(other: Money): Money; negate(): Money; abs(): Money; /** -1, 0 oder 1 */ compareTo(other: Money): number; equals(other: Money): boolean; isZero(): boolean; isPositive(): boolean; isNegative(): boolean; /** * Distributes the amount by weights (determinismus.md §2): largest-remainder, * tie → first part. Σ parts = amount, always. * * Weights: non-negative decimal values (number or string), sum > 0. * Negative amounts are distributed as a negated mirror image. */ allocate(...weights: Array): Money[]; /** Distribution into n equal parts (collective-item fifths, depreciation monthly rates). */ allocateEvenly(parts: number): Money[]; /** Amount as a string decimal with fixed scale, e.g. "1234.56" (datenformat.md). */ amountAsString(): string; toJSON(): { amount: string; currency: string; }; toString(): string; private static fromMinor; /** Bring decimal weights losslessly to integer weights of the same scale. */ private static normalizeWeights; private assertSameCurrency; } declare function canonicalJson(value: unknown): string; /** * Time source of the core (determinism hook). Deliberately a dedicated interface, * PSR-20-/Temporal-compatible in spirit: returns a point in time. */ interface Clock { now(): Date; } /** Real system time (production). */ declare class SystemClock implements Clock { now(): Date; } /** Fixed time for tests and deterministic runs. */ declare class FixedClock implements Clock { private current; private constructor(); static at(iso8601: string): FixedClock; now(): Date; advanceMilliseconds(milliseconds: number): void; } declare class Uuid { readonly value: string; private constructor(); static fromString(value: string): Uuid; static v7(clock?: Clock): Uuid; equals(other: Uuid): boolean; /** Byte-wise = chronological order for v7. */ compareTo(other: Uuid): number; toJSON(): string; toString(): string; } /** * The data-format version every export manifest carries. * * The spec is explicit that this always states the *current* spec version * (`datenformat.md`: „`formatVersion` trägt stets die aktuelle Spec-Version"). It sat at 0.4 while * the schema, the pack modules and the parameter contract had long moved to 0.6 — an export that * understated its own format, which is the one field a reading system uses to decide whether it * can read the file at all. * * It lives here, in one place per language, and a test asserts it matches the version in the * schema's `$id`. Bumping the schema without bumping this now turns a build red instead of * shipping a mislabelled export. */ declare const FORMAT_VERSION = "0.10"; /** * ID source of the core — port, so that tests and determinism runs can control * generation. Production: UUIDv7. */ interface IdGenerator { next(): Uuid; } /** Production generator: real UUIDv7 from time + random. */ declare class UuidV7IdGenerator implements IdGenerator { private readonly clock; constructor(clock?: Clock); next(): Uuid; } /** * UUIDv7-shaped IDs from a fixed clock + counter instead of random — for tests and the * double-run determinism check of the conformance suite (stream hashes contain * IDs; two runs must be byte-identical). */ declare class DeterministicIdGenerator implements IdGenerator { private readonly clock; private counter; constructor(clock: Clock); next(): Uuid; } /** Business-invalid value (amount, weight, code, UUID …). */ declare class InvalidValue extends Error { constructor(message: string); } /** Different currencies cannot be offset against each other. */ declare class CurrencyMismatch extends Error { constructor(message: string); } declare class CalendarDate { readonly iso: string; private constructor(); static of(value: string): CalendarDate; /** * Whole days from `other` up to this date; negative when this date is earlier. * Zoneless subtraction — no hours, no DST, no leap seconds to lose a day to. */ daysSince(other: CalendarDate): number; private dayNumber; compareTo(other: CalendarDate): number; equals(other: CalendarDate): boolean; isBefore(other: CalendarDate): boolean; isAfter(other: CalendarDate): boolean; isBetween(start: CalendarDate, end: CalendarDate): boolean; year(): number; month(): number; /** * The same day-of-month `months` later, clamped to the target month's last day (31 January plus * one month is 28 February, never 3 March). Zoneless like everything else here: this is calendar * arithmetic on three numbers, not a timestamp shifted by seconds. */ plusMonths(months: number): CalendarDate; lastDayOfMonth(): CalendarDate; firstDayOfNextMonth(): CalendarDate; toJSON(): string; toString(): string; } /** * Account number as a string — leading zeros significant (datenformat.md). * Comparison by Unicode code points, no locale collation: "10" < "9", * "0420" < "1200" < "8400" (determinismus.md §3). JS `<` on strings * compares UTF-16 code units = code points in the BMP. */ declare class AccountNumber { readonly value: string; private constructor(); static of(value: string): AccountNumber; compareTo(other: AccountNumber): number; equals(other: AccountNumber): boolean; toJSON(): string; toString(): string; } /** * Reference to a period: fiscal year + period number (datenformat.md * `periodRef`). `fiscalYear` = calendar year of the fiscal year end. */ declare class PeriodRef { readonly fiscalYear: number; readonly period: number; constructor(fiscalYear: number, period: number); /** Chronological: year first, then period. */ compareTo(other: PeriodRef): number; equals(other: PeriodRef): boolean; toJSON(): { fiscalYear: number; period: number; }; } /** * Additional allocation of a posting line: dimension type + value code * (datenformat.md). The validity check against master data happens at the * operation (E_DIMENSION_INVALID), not here. */ declare class DimensionValue { readonly type: string; readonly code: string; private constructor(); static of(type: string, code: string): DimensionValue; equals(other: DimensionValue): boolean; toJSON(): { type: string; code: string; }; } /** * Domain error with catalog code (fehlerkatalog.md). Contract part: same * violation → same code in all implementations. `message` is free-form, * `details` carries the IDs/values involved. */ declare class DomainError extends Error { readonly errorCode: string; readonly details: Record; constructor(errorCode: string, message?: string, details?: Record); } /** Debit/credit. */ type Side = 'debit' | 'credit'; /** * Account type determines the balance mechanics (ledger-modell.md): balance-sheet accounts * accumulate over years, income accounts per fiscal year. */ type AccountType = 'asset' | 'liability' | 'equity' | 'expense' | 'revenue'; /** Balance-sheet account: balance carries forward implicitly (no closing/opening account). */ declare function isBalanceCarrying(type: AccountType): boolean; declare function isAccountType(value: unknown): value is AccountType; type AccountStatus = 'active' | 'locked'; /** Record lifecycle: entered (correctable) → finalized (only reversal). */ type EntryStatus = 'entered' | 'finalized'; type PeriodStatus = 'open' | 'closed'; type FiscalYearStatus = 'open' | 'closed'; type OpenItemKind = 'receivable' | 'payable'; type OpenItemStatus = 'open' | 'partially_settled' | 'settled' | 'cancelled'; /** * Why an open item was settled (IMPL-008). `payment` is the ordinary case and the default when the * field is absent; `cancellation` arises only from `reverse` and means the item is done because its * origin entry was reversed — no money moved. Without the distinction a reversal is indistinguishable * from a payment, and cash-basis VAT would declare tax for money that never arrived. */ type SettlementCause = 'payment' | 'cancellation'; declare function parseSettlementCause(value: unknown): SettlementCause; /** * Settlement with difference (api.md G2): cash discount, bad debt, minor difference. * The difference must be materialized as explicit posting line(s). */ type SettlementDifferenceKind = 'discount' | 'bad_debt' | 'minor'; declare function parseSettlementDifferenceKind(value: unknown): SettlementDifferenceKind | null; declare function parseOpenItemKind(value: unknown): OpenItemKind | null; /** * The canonical subtypes an account may carry. * * **Why this is closed.** `subtype` is the field through which the chart tells the engine what an * account *is*: which movements are profit-neutral, which account is a tax account and on which * side its tax stands, which posting opens a receivable. The field was a free string, so a pack * that wrote `tax-out` instead of `tax_out` produced an account that looked annotated and was * inert — the VAT return simply skipped it, and nothing in the output said a tax account had gone * missing. That is the same defect the tax mechanisms were closed for in v0.8.0 (`reverse-charge` * fell back to plain VAT under the ordinary reporting key) and the one `PartnerKind` was closed * for (`custommer` was a partner kind like any other). Third time, same shape, same answer. * * **The stored value stays a string.** This union is the validator, not a change to the data * format — exactly as `PartnerKind` is. Nothing here changes what an export writes. * * **Where it is enforced, and deliberately where it is not.** At the two boundaries where a * subtype is *authored*: `createAccount`/`importChartOfAccounts` (`E_INPUT_INVALID` / * `E_COA_FORMAT_INVALID`) and pack resolution (`E_PACK_INCOHERENT`, so a composed pack fails at * `resolvePack` rather than at the first posting). It is **not** enforced in the `Account` * constructor, and that is not an oversight: hydrating a stored account runs through the same * constructor, so a database written before this repertoire existed would stop loading. A * validation that refuses to read what it once wrote is a worse failure than the one it prevents. * * **Two tiers, one list.** Eight of these the engine reads and branches on; `fixed_asset`, * `opening_balance` and `private` are annotation that every shipped pack carries and no code * consults. They are in the repertoire because the packs use them, and keeping them here is what * makes the list safe to check against — a repertoire that only held the eight would refuse the * three shipped charts. */ type AccountSubtype = /** Read: cash-basis (profit-neutral movement), payment account. */ 'bank' /** Read: cash journal (F-CORE-030), cash-basis. */ | 'cash' /** Read: cash-basis — money in transit is not a profit event. */ | 'transit' /** Read: a debit opens a receivable. */ | 'ar' /** Read: a credit opens a payable. */ | 'ap' /** Read: VAT return (input side), cash-basis, DATEV export. */ | 'tax_in' /** Read: VAT return (output side), cash-basis, DATEV export. */ | 'tax_out' /** Read: where an appropriated result lands (F-CORE-038). */ | 'result_allocation' /** Read: stock, the only account `valuateInventory` may value onto (F-CORE-050). */ | 'inventory' /** Read: provisions, the only account `recognizeProvision` may form one on (F-CORE-051). */ | 'provision' /** Annotation: the packs mark their asset accounts; the asset expansion uses its own module. */ | 'fixed_asset' /** Annotation: the opening-balance account of a chart. */ | 'opening_balance' /** Annotation: owner's drawings and contributions. */ | 'private'; /** * The repertoire, in declaration order. Published for the same reason `TaxMechanisms.all()` is: a * document that names the subtypes — `docs/handbuch` and the pack docs do — is making a checkable * claim, and a claim nothing checks goes stale. */ declare const ACCOUNT_SUBTYPES: readonly AccountSubtype[]; declare function isAccountSubtype(value: unknown): value is AccountSubtype; /** * Account (ledger-modell.md aggregate 2). No balance in the aggregate — balances are * projections of the journal, always. */ declare class Account { readonly id: Uuid; readonly number: AccountNumber; readonly name: string; readonly type: AccountType; readonly subtype: string | null; /** * The window in which the account may be POSTED to (F-CORE-045). `null` on either side means * unbounded, which is what every account had before the window existed and what almost every * account will keep. * * **Not the same thing as a lock, and that is why both exist.** A lock is unconditional and * about now: it stops every posting, including a correction dated before the lock, which is * exactly wrong for an account being retired at a year end. A window is about the posting's own * date, so an account valid to 2026-12-31 keeps accepting a late correction for December and * refuses January. */ readonly validFrom: CalendarDate | null; readonly validTo: CalendarDate | null; private accountStatus; constructor(id: Uuid, number: AccountNumber, name: string, type: AccountType, subtype: string | null, status?: AccountStatus, /** * The window in which the account may be POSTED to (F-CORE-045). `null` on either side means * unbounded, which is what every account had before the window existed and what almost every * account will keep. * * **Not the same thing as a lock, and that is why both exist.** A lock is unconditional and * about now: it stops every posting, including a correction dated before the lock, which is * exactly wrong for an account being retired at a year end. A window is about the posting's own * date, so an account valid to 2026-12-31 keeps accepting a late correction for December and * refuses January. */ validFrom?: CalendarDate | null, validTo?: CalendarDate | null); /** * Writes only, and never reads: an account outside its window still appears in every report that * has postings on it, because the history happened. A window that hid past figures would be a way * to make the books say less than the journal does. */ isValidOn(date: CalendarDate): boolean; status(): AccountStatus; isLocked(): boolean; lock(): void; /** * The way back (F-CORE-033). * * The lock had no counterpart for a long time, and the question that decided this was whether the * irreversibility was *law*. It is not. What the German rules protect against unrecognisable * change are **postings**; for master data they ask that the change be *logged* — which the audit * trail does, in both directions. And that is the German answer to a question no other * jurisdiction answers differently either: no chart of accounts anywhere is a one-way door. So * nothing here is a jurisdiction's answer and nothing belongs in a pack. The sources are in the * knowledge base (`knowledge/10-fachwissen/17-gobd-compliance.md`), where a statute may be named. * * What a lock protects is the books, and it keeps doing that: an account cannot be posted to * while it is locked, and every posting ever made on it stays exactly where it is. Unlocking * changes nothing about the past — it only allows a future posting again, which is why a * mis-clicked lock does not have to be repaired by abandoning the account and opening a second * one under a new number. */ unlock(): void; toJSON(): Record; } /** * Posting line — value object within the posting (no own identity; * reference = posting ID + line index). */ declare class EntryLine { readonly accountId: Uuid; readonly account: AccountNumber; readonly side: Side; readonly money: Money; readonly dimensions: DimensionValue[]; readonly taxTag: Record | null; constructor(accountId: Uuid, account: AccountNumber, side: Side, money: Money, dimensions?: DimensionValue[], taxTag?: Record | null); negated(): EntryLine; toJSON(): Record; } /** * Posting — the most important aggregate (ledger-modell.md). Created complete and * valid (validation in the ledger service); lifecycle entered → finalized, * afterwards only reversal (new posting with back-reference, full reversal). */ declare class JournalEntry { readonly id: Uuid; readonly sequenceNumber: number; readonly entryDate: CalendarDate; readonly voucherDate: CalendarDate | null; readonly recordedAt: string; readonly periodRef: PeriodRef; readonly voucherId: Uuid; readonly reverses: Uuid | null; private entryText; private entryLines; private entryReversedBy; private entryStatus; constructor(id: Uuid, sequenceNumber: number, entryDate: CalendarDate, voucherDate: CalendarDate | null, recordedAt: string, periodRef: PeriodRef, voucherId: Uuid, text: string, lines: EntryLine[], reverses?: Uuid | null, reversedBy?: Uuid | null, status?: EntryStatus); status(): EntryStatus; isFinalized(): boolean; text(): string; lines(): EntryLine[]; reversedBy(): Uuid | null; changeText(text: string): void; changeLines(lines: EntryLine[]): void; finalize(): void; markReversed(reversalId: Uuid): void; private assertCorrectable; toJSON(): Record; } /** * Voucher (ledger-modell.md aggregate 4): exists before/without a posting, several * postings can reference it. Metadata for cash-basis/tax projections (due, recurring, * economicYear, serviceDate/servicePeriod, partnerId, kind …). */ interface VoucherProps { readonly id: Uuid; readonly voucherNumber: string; readonly voucherDate: CalendarDate; readonly due?: CalendarDate | null; readonly recurring?: boolean; readonly economicYear?: number | null; readonly supplierTaxationMethod?: string | null; readonly serviceDate?: CalendarDate | null; readonly servicePeriodFrom?: CalendarDate | null; readonly servicePeriodTo?: CalendarDate | null; readonly kind?: string | null; readonly partnerId?: Uuid | null; readonly issuer?: string | null; } declare class Voucher { readonly id: Uuid; readonly voucherNumber: string; readonly voucherDate: CalendarDate; readonly due: CalendarDate | null; readonly recurring: boolean; readonly economicYear: number | null; readonly supplierTaxationMethod: string | null; readonly serviceDate: CalendarDate | null; readonly servicePeriodFrom: CalendarDate | null; readonly servicePeriodTo: CalendarDate | null; readonly kind: string | null; readonly partnerId: Uuid | null; readonly issuer: string | null; constructor(props: VoucherProps); /** Tax-relevant date: service date, fallback voucher date. */ taxDate(): CalendarDate; toJSON(): Record; } /** * Period — entity within the FiscalYear aggregate (gapless, * non-overlapping; status change only via the aggregate). */ declare class Period { readonly number: number; readonly start: CalendarDate; readonly end: CalendarDate; private periodStatus; constructor(number: number, start: CalendarDate, end: CalendarDate, status?: PeriodStatus); status(): PeriodStatus; isOpen(): boolean; contains(date: CalendarDate): boolean; /** Call only via FiscalYear (order check there). */ close(): void; /** Call only via FiscalYear (year status check there). */ reopen(): void; } interface PeriodDefinition { readonly period: number; readonly start: CalendarDate; readonly end: CalendarDate; } /** * Fiscal year with periods (ledger-modell.md aggregate 3). Invariants: * periods gapless and non-overlapping; closing only in order; * reopening only before year-end closing. `year` = calendar year of the fiscal year end. */ declare class FiscalYear { readonly id: Uuid; readonly year: number; readonly start: CalendarDate; readonly end: CalendarDate; private readonly periodList; private fiscalStatus; private constructor(); static create(id: Uuid, year: number, start: CalendarDate, end: CalendarDate, explicitPeriods?: PeriodDefinition[] | null): FiscalYear; /** * Restore from persistence: take over status and periods explicitly * (no rebuild, no validation) — counterpart to PHP's `FiscalYear::restore`. */ static restore(id: Uuid, year: number, start: CalendarDate, end: CalendarDate, status: FiscalYearStatus, periods: Period[]): FiscalYear; private static monthlyPeriods; status(): FiscalYearStatus; isClosed(): boolean; periods(): Period[]; period(number: number): Period; contains(date: CalendarDate): boolean; periodForDate(date: CalendarDate): Period; closePeriod(number: number): Period; reopenPeriod(number: number): Period; /** Pure status change — no business posting effect (api.md v0.3). */ close(): void; private assertNotClosed; } type AuditChanges = Record; /** * Audit entry (datenformat.md `auditLog.jsonl`): flat before/after diff of only the changed fields, * plus — since format 0.8 — its link in the trail's hash chain. * * **Why the chain is here and not on the journal entry.** `docs/gobd-conformance.md` §14 item 5c * asked for tamper evidence on the *trail*: today it is append-only because no code path updates or * deletes it, which is a property of the procedure rather than of the data — an auditor has to trust * the deployment instead of checking the file. The obvious-looking alternative, a chain over the * postings, is **not available**: `datenformat.md` reserves `previousEntryHash` on the posting and * says writers must not populate it in v0.x while readers must ignore it. A chain every conforming * reader is instructed to ignore is evidence for nobody (SPEC-022). The trail's records carry no * such reservation, and the trail is what the row asked about. * * **`recordHash` covers the record including its `previousRecordHash`**, so changing any earlier * record invalidates every later link. Removing a record breaks the link at its successor. What a * chain cannot do is notice records removed from the *end* — which is why the head is published. */ declare class AuditRecord { readonly id: Uuid; readonly at: string; readonly actor: string; readonly objectType: string; readonly objectId: Uuid; readonly action: string; readonly changes: AuditChanges; /** The predecessor's `recordHash`; `null` for the first record and for anything written before 0.8. */ readonly previousRecordHash: string | null; /** This record's own hash; `null` only for records written before 0.8. */ readonly recordHash: string | null; constructor(id: Uuid, at: string, actor: string, objectType: string, objectId: Uuid, action: string, changes?: AuditChanges, /** The predecessor's `recordHash`; `null` for the first record and for anything written before 0.8. */ previousRecordHash?: string | null, /** This record's own hash; `null` only for records written before 0.8. */ recordHash?: string | null); /** * The same record, linked behind `previousHash` and carrying its own hash. * * The trail computes this at `append`, because the head is the one thing only the store knows. */ chainedTo(previousHash: string | null): AuditRecord; /** * What is left of a record after an erasure (F-CORE-040): the link, and nothing else. * * **A lawful erasure and a manipulation must not look alike.** Deleting the row outright would * break the chain at the successor and leave it broken for good, so every later verification would * report tampering that never happened — and a warning that is always on is a warning nobody * reads. The shell keeps `previousRecordHash` and `recordHash` so the linkage stays checkable, and * drops everything the erasure was about. * * The honest limit, and it is the point rather than a flaw: a shell's *content* can no longer be * verified against its hash, because there is no content left to hash. Linkage stays provable, * content integrity of the erased record does not. If it did, the erasure would not be one. */ redactedShell(): AuditRecord; isRedacted(): boolean; toJSON(): Record; } /** Low-value-asset switch (SF-05): three routes; the thresholds are rule-module data. */ type AssetRoute = 'capitalize' | 'immediate_expense' | 'pool'; declare function parseAssetRoute(value: unknown): AssetRoute | null; /** * Asset (assets-modell.md): master data + depreciation schedule + history. Invariants: * book value = acquisition cost − Σ depreciations, never < 0; no depreciation before acquisition/after disposal. */ declare class Asset { readonly id: Uuid; readonly name: string; readonly assetClass: string; readonly assetAccount: AccountNumber; readonly acquisitionCost: Money; readonly acquiredOn: CalendarDate; readonly route: AssetRoute; readonly usefulLifeMonths: number | null; monthlySchedule: Money[]; readonly voucherId: Uuid; /** * Cost centre and friends, carried by the asset itself (IMPL-023). Depreciation is booked by the * machine, month after month, for years — nobody is there to name a dimension at that moment, * and a mandatory one on the depreciation account would otherwise make the run impossible. The * master record answering it once is also how it works in practice: an asset belongs to a cost * centre, and its depreciation belongs there with it. */ readonly dimensions: ReadonlyArray<{ type: string; code: string; }>; /** * First month of the depreciation plan. Normally the month of acquisition — pro rata temporis, * which is what linear depreciation asks for in most jurisdictions. * * A pooled asset can be different: where a jurisdiction dissolves its pool in equal *fiscal-year* * fractions, the first year is not shortened by the acquisition month, so the plan starts at the * beginning of the fiscal year the asset was acquired in. Which of the two applies is pack data * (`poolProRataInFirstYear`), never a decision of this class — the asset is simply told where its * plan begins. * * Null means "same as acquisition", so persisted assets written before this field existed * rehydrate to exactly the behaviour they had. */ readonly depreciationStart: CalendarDate | null; /** * How the schedule was built. Straight line spreads the cost flat, so the yearly run can * re-derive a year's share from the month counts; declining balance cannot, because each year * depends on what is left after the one before. The schedule then IS the plan and has to be read * rather than recomputed — that is the only thing this field decides. * * Null means straight line, so assets written before the field existed rehydrate unchanged. */ readonly depreciationMethod: string | null; /** * An additional allowance running ALONGSIDE the plan, not instead of it. * * Some jurisdictions let a business deduct an extra share of the cost within the first few years, * freely distributed over them. It is not a depreciation method — the ordinary plan carries on * untouched on the original basis while the window is open — so it cannot be expressed as a * different schedule. It is a budget: this much may still be taken, until this fiscal year. Both * numbers come from the pack; the asset only remembers them. * * Null means no such allowance was elected, which is every asset written before this existed. */ readonly specialDepreciationBudget: Money | null; readonly specialDepreciationWindowEnd: number | null; /** * Total expected output of the asset, where depreciation follows use rather than time — * kilometres for a lorry, operating hours for a press, copies for a machine. * * It changes what "a year" means. Time-based depreciation knows at acquisition what every future * period will take; output-based depreciation cannot, because the number comes from outside the * books. So there is no schedule to build and the yearly run has nothing to do here: usage is * reported as it happens, and each report books the difference between what the asset has now * given and what has already been written off. */ readonly totalUnits: number | null; private readonly depreciations; /** * True once an unplanned write-down has rewritten the remaining plan. From then on the schedule IS * the plan and may not be re-derived from the acquisition cost — the whole point of the write-down * is that the cost is no longer the basis. */ private scheduleRevised; private reportedUnitsValue; /** * The schedule as it stood before any write-down rebased it — the *shadow* plan. * * It exists for one purpose: the write-up ceiling. A write-down does not only reduce the book * value, it lowers every remaining planned instalment, so the book value drifts *above* what it * would have been without the write-down as the plan runs on. Reversing the write-down in full * would therefore carry the asset higher than its amortised acquisition cost, which no write-up * may do. The ceiling is `cost − Σ original shares of the booked months`, and the original shares * are recoverable from nothing else once a rebase has happened: for a declining-balance or * units-of-production plan they were never a flat allocate to begin with. */ private originalSchedule; private disposed; private disposedOn; constructor(id: Uuid, name: string, assetClass: string, assetAccount: AccountNumber, acquisitionCost: Money, acquiredOn: CalendarDate, route: AssetRoute, usefulLifeMonths: number | null, monthlySchedule: Money[], voucherId: Uuid, /** * Cost centre and friends, carried by the asset itself (IMPL-023). Depreciation is booked by the * machine, month after month, for years — nobody is there to name a dimension at that moment, * and a mandatory one on the depreciation account would otherwise make the run impossible. The * master record answering it once is also how it works in practice: an asset belongs to a cost * centre, and its depreciation belongs there with it. */ dimensions?: ReadonlyArray<{ type: string; code: string; }>, /** * First month of the depreciation plan. Normally the month of acquisition — pro rata temporis, * which is what linear depreciation asks for in most jurisdictions. * * A pooled asset can be different: where a jurisdiction dissolves its pool in equal *fiscal-year* * fractions, the first year is not shortened by the acquisition month, so the plan starts at the * beginning of the fiscal year the asset was acquired in. Which of the two applies is pack data * (`poolProRataInFirstYear`), never a decision of this class — the asset is simply told where its * plan begins. * * Null means "same as acquisition", so persisted assets written before this field existed * rehydrate to exactly the behaviour they had. */ depreciationStart?: CalendarDate | null, /** * How the schedule was built. Straight line spreads the cost flat, so the yearly run can * re-derive a year's share from the month counts; declining balance cannot, because each year * depends on what is left after the one before. The schedule then IS the plan and has to be read * rather than recomputed — that is the only thing this field decides. * * Null means straight line, so assets written before the field existed rehydrate unchanged. */ depreciationMethod?: string | null, /** * An additional allowance running ALONGSIDE the plan, not instead of it. * * Some jurisdictions let a business deduct an extra share of the cost within the first few years, * freely distributed over them. It is not a depreciation method — the ordinary plan carries on * untouched on the original basis while the window is open — so it cannot be expressed as a * different schedule. It is a budget: this much may still be taken, until this fiscal year. Both * numbers come from the pack; the asset only remembers them. * * Null means no such allowance was elected, which is every asset written before this existed. */ specialDepreciationBudget?: Money | null, specialDepreciationWindowEnd?: number | null, /** * Total expected output of the asset, where depreciation follows use rather than time — * kilometres for a lorry, operating hours for a press, copies for a machine. * * It changes what "a year" means. Time-based depreciation knows at acquisition what every future * period will take; output-based depreciation cannot, because the number comes from outside the * books. So there is no schedule to build and the yearly run has nothing to do here: usage is * reported as it happens, and each report books the difference between what the asset has now * given and what has already been written off. */ totalUnits?: number | null); /** Units reported so far — never more than `totalUnits`, which is what caps the last booking. */ reportedUnits(): number; recordUsage(date: CalendarDate, amount: Money, entryId: Uuid, unitsAfter: number): void; /** What is left of the additional allowance. */ specialDepreciationRemaining(): Money | null; hasSpecialDepreciation(): boolean; recordSpecialDepreciation(date: CalendarDate, amount: Money, entryId: Uuid): void; /** * Spreads whatever book value is left over the plan months not yet booked. * * Two occasions need exactly this: an unplanned write-down, where the basis fell; and the end of an * additional allowance's window, where part of the cost has already been deducted outside the plan * and the rest has to last for the remaining life. Same arithmetic, and it should stay the same * arithmetic — two spreadings that drifted apart would be two different answers to one question. */ rebaseRemainingPlan(openPlanMonths: number[]): void; /** Straight line unless the pack offered, and the caller chose, something else. */ method(): string; /** A schedule that cannot be re-derived from month counts and must be read as it stands. */ scheduleIsAuthoritative(): boolean; scheduleWasRevised(): boolean; /** Where the depreciation plan begins — the acquisition month unless the pack moved it. */ planStart(): CalendarDate; /** When it left, or null while it is still there — read by the movement schedule (F-CORE-055). */ disposedOnDate(): CalendarDate | null; isDisposed(): boolean; assertActive(): void; dispose(disposedOn: CalendarDate): void; planMonthDate(planMonth: number): CalendarDate; isMonthBooked(planMonth: number): boolean; recordDepreciation(planMonth: number, date: CalendarDate, amount: Money, entryId: Uuid, kind?: string): void; /** * An unplanned write-down: the amount lowers the book value at once, and what is left is spread over * the plan months that have not been booked yet. * * Re-spreading is the part that is easy to leave out and wrong to leave out. Continuing the old plan * after a write-down would depreciate more than the asset is still worth — the invariant says the * book value never goes below zero — and stopping early instead would finish the asset before its * life is over. Neither is what a lasting impairment means: the reduced value is carried on over the * REMAINING life, which is exactly what this does. * * `planMonth: 0` marks it as belonging to no plan month; plan months are 1-based, so nothing reads * it as one, while the accumulated depreciation picks it up like any other booking. */ recordWriteDown(date: CalendarDate, amount: Money, entryId: Uuid, openPlanMonths: number[]): void; /** * A write-up reverses part of an earlier write-down. It is recorded as a negative "unplanned" * booking, so every existing reader — accumulated depreciation, the book value, the register — * picks it up without a special case, and the plan is rebased upward the same way a write-down * rebases it downward. */ recordWriteUp(date: CalendarDate, amount: Money, entryId: Uuid, openPlanMonths: number[]): void; /** * What the book value would be if no write-down had ever happened — the ceiling a write-up may * not cross. * * Every month the live plan has booked is charged at its **original** share instead of the reduced * one. An asset that was never written down has an identical shadow, so this equals the ordinary * book value and the ceiling binds nothing. */ amortisedCostCeiling(): Money; /** What has been written down and not yet written back — nothing may be reversed twice. */ unreversedWriteDowns(): Money; originalScheduleShares(): Money[]; /** Depreciation history in persistable form — counterpart to PHP's `depreciationsForPersistence`. */ depreciationsForPersistence(): Array<{ planMonth: number; date: string; amount: { amount: string; currency: string; }; entryId: string; kind: string; }>; /** * Restore from persistence: set master data + depreciation history + disposal * status directly (no re-validation) — counterpart to PHP's `Asset::restore`. */ static restore(id: Uuid, name: string, assetClass: string, assetAccount: AccountNumber, acquisitionCost: Money, acquiredOn: CalendarDate, route: AssetRoute, usefulLifeMonths: number | null, monthlySchedule: Money[], voucherId: Uuid, depreciations: ReadonlyArray<{ planMonth: number; date: CalendarDate; amount: Money; entryId: Uuid; kind?: string; }>, disposed: boolean, disposedOn: CalendarDate | null, dimensions?: ReadonlyArray<{ type: string; code: string; }>, depreciationStart?: CalendarDate | null, depreciationMethod?: string | null, scheduleRevised?: boolean, specialDepreciationBudget?: Money | null, specialDepreciationWindowEnd?: number | null, totalUnits?: number | null, reportedUnits?: number, originalSchedule?: Money[] | null): Asset; accumulatedDepreciationAt(asOf: CalendarDate | null): Money; /** * Carrying amount = cost less what has been depreciated (IMPL-024). * * Only an immediately expensed asset has no carrying amount — it was never capitalised. A * *pooled* one was: it sits on the pool account and is written down over the pack's term, so * reporting zero for it made the fixed-asset schedule (F-AST-005) understate the balance sheet * it is supposed to explain. The old shortcut was invisible while nothing consumed the value * for pooled assets; the disposal write-off does now. */ bookValueAt(asOf: CalendarDate | null): Money; scheduleSummary(): Record; toJSON(): Record; } /** * Writes the audit trail for the ledger services. * * Extracted because `Ledger`, `SettlementService` and `ChartAdminService` all need the same three * things — who acted, what time it is, and the record itself — and sharing them through a base * class would tie services together that otherwise have nothing in common. */ declare class AuditWriter { private readonly audit; private readonly clock; private readonly ids; constructor(audit: AuditTrail, clock: Clock, ids: IdGenerator); /** The actor of an operation; absent or empty means the system itself. */ actorOf(input: Record): string; now(): string; record(actor: string, objectType: string, objectId: Uuid, action: string, changes?: AuditChanges): void; } /** * Keeps a tenant's configuration where the books already live (SPEC-015). * * Six operations change configuration — `setTaxProfile`, `defineDimensionType`, * `defineDimensionValue`, `setAllocationScheme`, `importMapping` and `setEntityProfile` — and until * this existed each of them changed a live object and nothing else. They audited the change durably all the same, which * is the part that made it a defect rather than a limitation: the trail stated something the books * stopped carrying at the next restart. * * The store is the one place that writes. Each `remember…` is called by the service that just * succeeded, never before, so a rejected operation stores nothing — the same discipline the CLI's * `rememberMapping` had to invent for one of the five. */ declare class TenantConfigStore { private readonly repository; private readonly record; constructor(repository: TenantRecordRepository, record: TenantRecordData); rememberTaxProfile(profile: Record): void; rememberDimensions(types: Array<{ code: string; }>, values: Array<{ typeCode: string; code: string; }>): void; rememberEntityProfile(profile: { legalForm: string; sizeClass: string | null; }): void; rememberAllocationScheme(scheme: Record): void; /** * Replace by id rather than append: importing the same id twice must update it, not leave two * mappings behind that the next load would read as overlapping. (The rule comes from the CLI, * which learned it the hard way; it belongs here now that the library does the storing.) */ rememberMapping(mapping: Record): void; /** What is stored right now — the seed written at first open, or what the operations made of it. */ snapshot(): TenantRecordData; private flush; } /** * Opens a tenant's stored configuration, seeding it on first open. * * **The stored record wins.** What the caller passes at construction is a *seed*: it is written on * the first open of a tenant that has no record yet, and ignored on every open after that. The * alternative — the arguments win when given — sounds harmless and is the state this whole finding * came out of: an embedding's configuration file and the library's tables would both claim to hold * the truth, and the two would drift the first time an operation changed one of them. * * Living with the rule is what makes `defineDimensionType` work: an embedding that used to pass its * cost centres in AND declare them (and got `E_DIMENSION_INVALID` for its trouble) can now do * either one. * * The rule lives in the core, not in the adapters, so both languages cannot answer it differently. */ declare function openTenantConfiguration(repository: TenantRecordRepository, seed: TenantRecordData): { record: TenantRecordData; store: TenantConfigStore; seeded: boolean; }; /** An empty configuration block — the shape every record carries, with nothing configured yet. */ declare function emptyTenantConfig(): TenantRecordData['config']; interface OverheadRate { costCenter: string; label: string; overhead: string; base: string; rate: string | null; } interface RateWarning { costCenter: string; reason: string; } interface ProductionCostComponent { id: string; amount: string; treatment: string; included: boolean; } interface ProductionCostResult { total: string; components: ProductionCostComponent[]; } declare class CostingService { private readonly baseCurrency; private readonly accounts; private readonly journal; /** * Where the runs live (F-KLR-001/004). * * They used to live in two private `Map`s in this class — the runs themselves and a per-period * version counter — which meant a released run was gone with the process that produced it and * the version restarted at 1 after every restart. The requirements had said otherwise all * along: runs are versioned per period, and the BAB and the rates are a projection *of a * released run*. A run no later process can read satisfies neither; it satisfies them inside * one process, which is not what a repository port is for. */ private readonly runs; private readonly ids; private readonly tenantId; private readonly audit; /** Where the scheme is kept, so it outlives this object (SPEC-015). */ private readonly configStore; private schemeSteps; private method; /** * The pack's answer to "what may enter production cost". Data, never code — which components a * jurisdiction requires, permits or forbids is the part that differs, and the summation is not. */ private ruleModule; /** * Production-cost component definitions and the tenant's election among the optional ones, frozen * into each run for the same reason the rates are. */ private productionCostConfig; /** * Overhead rate definitions, part of the same tenant-level configuration as the scheme. * * They sit on `setAllocationScheme` rather than on an operation of their own because a rate is * computed FROM an allocation and frozen INTO the run: a second operation would need its own * freezing rule against the same run, and two rules for one moment is how they drift apart. */ private rateDefinitions; /** A stored scheme waiting for its first use — see `restoreAllocationScheme`. */ private pendingScheme; /** * The scheme as it was given, kept for `tenantConfiguration` to report. * * The raw input rather than the parsed fields, for the same reason the store keeps the raw input: * it is exactly what `setAllocationScheme` accepts, so what comes out can be put back in and no * second serializer can drift from the first one. */ private schemeData; constructor(baseCurrency: Currency, accounts: AccountRepository, journal: JournalRepository, /** * Where the runs live (F-KLR-001/004). * * They used to live in two private `Map`s in this class — the runs themselves and a per-period * version counter — which meant a released run was gone with the process that produced it and * the version restarted at 1 after every restart. The requirements had said otherwise all * along: runs are versioned per period, and the BAB and the rates are a projection *of a * released run*. A run no later process can read satisfies neither; it satisfies them inside * one process, which is not what a repository port is for. */ runs: CostingRunRepository, ids: IdGenerator, tenantId?: Uuid | null, audit?: AuditWriter | null, /** Where the scheme is kept, so it outlives this object (SPEC-015). */ configStore?: TenantConfigStore | null); /** The resolved pack bundle (`productionCost` is read here). */ setRuleModule(ruleModule: Record): void; /** * Hands back a stored scheme when a tenant is opened (SPEC-015). * * **Deferred on purpose.** A scheme can reference production-cost treatments, which only the pack * answers — and the pack arrives through `setRuleModule`, *after* the factory has built the * tenant. Applying it here would make opening the books fail on a scheme that was perfectly valid * when it was set, which is the wrong moment to find out and the wrong thing to block: reading a * journal does not need an allocation scheme. * * So it is applied on first use — `setAllocationScheme` and `run` are the only entry points that * read it. A stored scheme that the *current* pack no longer accepts then fails when somebody runs * a costing, with the error the operation itself would have given. */ restoreAllocationScheme(input: Record): void; /** Applies what `restoreAllocationScheme` handed back, once, at the moment it is first needed. */ private applyPendingScheme; setAllocationScheme(input: Record): Record; /** * The allocation scheme in force, as it was set — what `tenantConfiguration` reports, or null when * none was ever set. * * Reads the *pending* scheme first and deliberately does not apply it. A stored scheme may name * production-cost treatments only the current pack answers, so applying it is what * `restoreAllocationScheme` defers to first use — and a projection is the wrong place to find out: * reporting what a tenant is configured as must not fail on a scheme that was valid when it was * set. Reporting it unapplied is the honest answer, because unapplied is what it is. */ allocationScheme(): Record | null; private applyAllocationScheme; run(input: Record): CostingRun; release(input: Record): CostingRun; costAllocationSheet(params: Record): Record; /** * Overhead rates of a run (F-KLR-004: "BAB *und Kalkulationssätze*"). * * A rate answers the question the allocation sheet cannot: the sheet says what a cost centre ended * up carrying, a rate says how that attaches to a product. Numerator is the centre after allocation, * denominator is a base the scheme declares — direct-cost ACCOUNTS, other cost CENTRES, or both. The * classic four fall straight out of that: material and production overhead over their direct costs, * administration and sales overhead over cost of production, which is "the direct-cost accounts plus * the two centres" and needs no special case. * * Rounded to four decimals, half-up away from zero — a rate is a published figure, not money, and * four places is where the pack schema already puts a percentage. * * Note what this deliberately does NOT do: refuse a draft run. F-KLR-001 says evaluations read * released runs only, and the fixture `parameter-effect` reads a draft sheet — an append-only * contract that says otherwise. Bending the fixture would rewrite what the contract always said, so * the rule is followed as the contract has it and the contradiction is recorded (FINDINGS-CLOSED.md). * `status` is in the answer, so nobody has to guess which they got. */ overheadRates(params: Record): Record; /** * Production cost of a run, component by component (F-KLR-004's balance-sheet neighbour). * * Every configured component appears, whether it was counted or not, with the pack's treatment next * to it — `mandatory`, `optional` or `forbidden` — and whether it went in. A valuation that shows * only its own total is unauditable; this one shows what it left out and on whose authority. */ productionCost(params: Record): Record; /** * Production cost of the period — the figure inventory is carried at. * * This is the one piece of cost accounting with balance-sheet effect, so what may be counted into it * is law rather than preference. The split falls exactly along the socket/plug line the rest of the * core uses — **this method sums, the pack says what may enter.** One jurisdiction requires material * and production cost with their overhead and the production-related depreciation, leaves general * administration to the preparer, and forbids research and distribution; another treats general * administration as a period charge and so reaches a different inventory value from identical books. * None of that is written here, and none of it should be: which of the three treatments a component * gets is a row in the pack, not a branch in this method. * * Three rules, and each refuses rather than guesses: * * - a component the pack does not declare is `E_PACK_INCOHERENT` — an unknown component silently * counted or silently dropped would move the balance sheet either way; * - electing a component the pack forbids is `E_INPUT_INVALID`, not a quiet exclusion, because the * caller has said something about their books that is not allowed; * - asking for the figure without configuring the components is `E_INPUT_INVALID` rather than 0.00, * since a valuation nobody set up is not a valuation of zero. * * What this deliberately does NOT do is divide by a quantity. Production cost per unit needs produced * quantities, and the core carries no quantities at all — goods movements and production orders are * the embedding application's data. summae answers what the components add up to and why; the * division is arithmetic on top of that. */ private computeProductionCost; /** * Applies the pack's treatment table to a production-cost configuration — at the moment the * configuration is set, not at the moment the run happens, so a caller learns about a refusal where * they can still do something about it. */ private resolveProductionCost; /** component id -> mandatory | optional | forbidden */ private treatmentTable; private computeRates; /** * One pass in step order. Cheap, and wrong the moment two centres serve each other — which is why a * cycle is refused here rather than resolved by picking an order. */ private allocateByStepLadder; /** * All centres at once, solved exactly (solveSimultaneously) and only then turned back into money. * * The order matters and is the reason this is not simply "solve and round": the solution is a vector * of exact fractions whose sum is the primary total to the last cent, and rounding each one on its * own would break that — a cent appears or vanishes, and the sheet no longer says that allocation * distributes rather than creates. So the fractions are floored and the difference handed out by * largest remainder, ties to the earlier cost centre, which is `Money.allocate`'s rule applied to a * vector instead of a single amount. */ private allocateSimultaneously; /** Minor units back to Money, exactly — no rounding is left to do at this point. */ private moneyFromMinor; private serializeTotals; /** * The next version for a period: one more than the highest stored for it. * * Derived rather than counted, because a counter in this object starts at zero in every new * process — the second run of a period would have claimed to be its first, and F-KLR-001's * "versioned per period" would have held only until a restart. */ private nextVersionFor; private requireRun; private assertAcyclic; } /** * Costing run (costing-modell.md aggregate 1): unique per period + version; * repetition creates a new version. draft → released. */ declare class CostingRun { readonly id: Uuid; readonly period: PeriodRef; readonly version: number; readonly primary: Map; readonly afterAllocation: Map; readonly grandTotal: Money; readonly method: string; readonly rates: OverheadRate[]; readonly rateWarnings: RateWarning[]; readonly productionCost: ProductionCostResult | null; private runStatus; constructor(id: Uuid, period: PeriodRef, version: number, primary: Map, afterAllocation: Map, grandTotal: Money, method?: string, rates?: OverheadRate[], rateWarnings?: RateWarning[], productionCost?: ProductionCostResult | null); status(): string; release(): void; /** * Persistable form (F-KLR-001/004). * * A run used to live in a `Map` inside the service, which meant a released run was gone with the * process that made it. The requirements had said otherwise all along — runs are versioned per * period, and the BAB and the rates are a projection *of a released run* — so a run no later * process can read satisfies neither. Everything the three projections read is in here, and * nothing else is: what a run answers must not depend on configuration that has moved since. */ toJSON(): Record; /** Restore from persistence — status taken over directly, no re-validation. */ static restore(id: Uuid, period: PeriodRef, version: number, status: string, primary: Map, afterAllocation: Map, grandTotal: Money, method: string, rates: OverheadRate[], rateWarnings: RateWarning[], productionCost: ProductionCostResult | null): CostingRun; /** * Cost-centre totals as an object, keys sorted by code point. * * A JSON object preserves insertion order, so writing the map as it happens to be iterated would * make the stored bytes depend on the order the postings arrived in — and the export has to be * byte-identical across implementations (SF-15). */ private static totalsToJSON; } interface InventoryCategoryRow { account: string; quantity: string; unitCost: string; marketValue: string | null; unitValue: string; source: string; openingValue: string; closingValue: string; change: string; changeAccount: string; writtenDownToMarket: boolean; } /** * One act of valuing stock, recorded (F-CORE-050). * * **What this is not, first, because that is the whole design.** It is not a stock ledger. summae * does not know what is in the warehouse and never claims to: quantities arrive as *input* to a * valuation and are not carried forward, there are no goods movements, no bills of material and no * product master. Those are the embedding application's data. * * **What it is** is the same thing `Asset` is one layer over: summae does not own the machine, it * owns the register and the postings. Here it owns the *act of valuing* — which accounts, which * quantities, at what unit value, where that value came from, what the comparison with a market * value did, and which entry it produced. Keeping that is not optional bookkeeping tidiness: an * engine that posts a change in stock and keeps no record of how it reached the number has done * exactly what this project refuses to let an embedder do, one level down. * * **Versioned per period, like a costing run, and for the same reason.** Repetition creates a new * version rather than overwriting one. The posting is always the *difference* to what the accounts * already carry, so a second valuation of an unchanged period books nothing and records that it * booked nothing (`entryId: null`) — self-correcting rather than duplicating, without an * idempotency key to get wrong. * * Every figure is a string at the currency's scale. The quantity is a string too: it is not Money, * it is not rounded, and it must survive a round trip through JSON byte-identically in both * languages. */ declare class InventoryValuation { readonly id: Uuid; readonly period: PeriodRef; readonly version: number; readonly valuationDate: CalendarDate; readonly runId: Uuid | null; readonly categories: InventoryCategoryRow[]; readonly closingTotal: Money; readonly change: Money; readonly entryId: Uuid | null; constructor(id: Uuid, period: PeriodRef, version: number, valuationDate: CalendarDate, runId: Uuid | null, categories: InventoryCategoryRow[], closingTotal: Money, change: Money, entryId: Uuid | null); /** * Persistable form. * * Frozen, like a released costing run: the categories carry the unit values the act used, not the * ones the configuration would produce today. A valuation that answers differently tomorrow is * not a valuation of anything. */ toJSON(): Record; static restore(id: Uuid, period: PeriodRef, version: number, valuationDate: CalendarDate, runId: Uuid | null, categories: InventoryCategoryRow[], closingTotal: Money, change: Money, entryId: Uuid | null): InventoryValuation; } interface DeferralInstalment { fiscalYear: number; period: number; amount: Money; } interface DeferralRelease extends DeferralInstalment { date: CalendarDate; entryId: Uuid; } /** The closed vocabulary — a third kind would be a third direction of posting. */ declare const DEFERRAL_KINDS: readonly string[]; /** * A prepaid or deferred item and its release plan (F-CORE-053). * * **What was missing was never the accounts.** Both of them have existed in the shipped German chart * from the beginning, and both have had a balance-sheet position. What was missing is the *plan*: an * insurance premium paid in December for the following year could be deferred and then had to be * released by hand, month after month, from memory. That is precisely the failure mode * `runDepreciation` exists to prevent for arithmetic that is identical — an amount spread evenly * over a known number of periods — and the two ought not to differ in whether the machine remembers. * * **The plan is fixed at recognition and never recomputed.** `allocate` distributes the amount over * the periods with largest-remainder, so the instalments sum to the amount exactly and the last one * carries no drift. Storing the plan rather than re-deriving it is the same decision the asset * schedule makes, for the same reason: a plan that is recomputed on read answers differently after a * rounding rule moves. * * **Two kinds, and they are opposites rather than variants.** A *prepaid expense* is money already * paid for a service still to come — an asset. A *deferred income* is money already received for a * service still to be rendered — a liability. Both defer, in opposite directions, and every posting * this class produces flips with the kind. */ declare class Deferral { readonly id: Uuid; readonly kind: string; readonly reason: string; readonly account: AccountNumber; readonly counterAccount: AccountNumber; readonly recognizedOn: CalendarDate; readonly amount: Money; readonly plan: DeferralInstalment[]; readonly recognitionEntryId: Uuid | null; private released; constructor(id: Uuid, kind: string, reason: string, account: AccountNumber, counterAccount: AccountNumber, recognizedOn: CalendarDate, amount: Money, plan: DeferralInstalment[], recognitionEntryId?: Uuid | null); isReleased(fiscalYear: number, period: number): boolean; recordRelease(fiscalYear: number, period: number, amount: Money, date: CalendarDate, entryId: Uuid): void; releasedTotal(): Money; outstanding(): Money; isSettled(): boolean; /** The instalment due for a period, or null where the plan has none. */ instalmentFor(fiscalYear: number, period: number): Money | null; releases(): DeferralRelease[]; toJSON(): Record; static restore(id: Uuid, kind: string, reason: string, account: AccountNumber, counterAccount: AccountNumber, recognizedOn: CalendarDate, amount: Money, plan: DeferralInstalment[], released: DeferralRelease[], recognitionEntryId: Uuid | null): Deferral; } interface ProvisionMovement { kind: string; date: CalendarDate; amount: Money; entryId: Uuid | null; note: string | null; } /** * A provision, with its history (F-CORE-051). * * **Why this is an aggregate and not just a balance.** The balance of a provision account answers * almost nothing an auditor asks. What was set aside, for what, when, at what estimate — and then * what happened to it: was it *used* because the obligation materialised, *released* because the * reason ceased, or *re-measured* because the estimate moved? Those are three different events with * three different postings and three different meanings, and a netted balance shows none of them. * Same reason the asset register exists next to the asset accounts. * * **The movement list is the point, and it is append-only.** Every step names the entry it produced, * so the register and the journal can be walked against each other in both directions. Nothing here * is ever edited: a re-measurement is a new movement, not a corrected old one. * * **`settled` is not `released`.** A provision reaches `settled` when its carrying amount is zero, * however it got there. *How* it got there is in the movements, and the difference matters: a * release is income the business never had to pay, a use is an obligation that came true. A status * field that collapsed the two would be the same kind of lie as a netted balance. */ declare class Provision { readonly id: Uuid; readonly reason: string; readonly account: AccountNumber; readonly expenseAccount: AccountNumber; readonly releaseAccount: AccountNumber; readonly recognizedOn: CalendarDate; readonly dueDate: CalendarDate | null; /** The undiscounted best estimate of the amount needed to settle (the *Erfüllungsbetrag*). */ readonly settlementAmount: Money; /** The rate the discount used, as a percentage string, or null where nothing was discounted. */ readonly discountRate: string | null; private movementList; private carrying; constructor(id: Uuid, reason: string, account: AccountNumber, expenseAccount: AccountNumber, releaseAccount: AccountNumber, recognizedOn: CalendarDate, dueDate: CalendarDate | null, /** The undiscounted best estimate of the amount needed to settle (the *Erfüllungsbetrag*). */ settlementAmount: Money, /** What was actually recognised — the present value where discounting applied. */ recognizedAmount: Money, /** The rate the discount used, as a percentage string, or null where nothing was discounted. */ discountRate: string | null); carryingAmount(): Money; isSettled(): boolean; status(): string; movements(): ProvisionMovement[]; record(kind: string, date: CalendarDate, amount: Money, entryId: Uuid | null, note?: string | null): void; /** * Move the carrying amount. Never below zero — a provision that has given back more than it held * would be income invented out of a sign error, and the operations that call this each cap their * own amount, so reaching here is a bug rather than a user mistake. */ moveCarryingAmount(delta: Money): void; toJSON(): Record; /** * Restore from persistence — the carrying amount and the movements are taken over directly, no * replay. Replaying them would recompute a history that is already a fact. */ static restore(id: Uuid, reason: string, account: AccountNumber, expenseAccount: AccountNumber, releaseAccount: AccountNumber, recognizedOn: CalendarDate, dueDate: CalendarDate | null, settlementAmount: Money, carryingAmount: Money, discountRate: string | null, movements: ProvisionMovement[]): Provision; } /** * Business partner (datenformat.md v0.4) — deliberately lean: OP-per-partner, * intra-community supply proof (VAT ID), EC sales list basis, DATEV master data. */ declare class Partner { readonly id: Uuid; private accountNumbers; private address; /** * Whether the partner is still in use (F-CORE-034). * * There is no `deletePartner` and there should not be: the books keep what they referenced, * and an id that open items point at must not vanish. But a partner nobody trades with any * more has to be sayable somewhere, or every application invents its own list beside the * ledger — which is the state that made this a finding. * * It is a **state, not a control**. An inactive partner refuses nothing: old items still * settle, old vouchers still read, and a posting that names one is not rejected. Whether a * picker still offers it is the application's workflow, and summae's own scope rule puts * "the user must…" on that side. Compare `Account.lock`, which does have teeth — the * difference is deliberate and the two words are different on purpose. */ private partnerStatus; private partnerName; private partnerKind; private partnerVatId; private partnerPaymentTermsDays; constructor(id: Uuid, name: string, kind: string, vatId: string | null, paymentTermsDays: number | null, accountNumbers?: string[], address?: Record, /** * Whether the partner is still in use (F-CORE-034). * * There is no `deletePartner` and there should not be: the books keep what they referenced, * and an id that open items point at must not vanish. But a partner nobody trades with any * more has to be sayable somewhere, or every application invents its own list beside the * ledger — which is the state that made this a finding. * * It is a **state, not a control**. An inactive partner refuses nothing: old items still * settle, old vouchers still read, and a posting that names one is not rejected. Whether a * picker still offers it is the application's workflow, and summae's own scope rule puts * "the user must…" on that side. Compare `Account.lock`, which does have teeth — the * difference is deliberate and the two words are different on purpose. */ partnerStatus?: 'active' | 'inactive'); name(): string; status(): 'active' | 'inactive'; isActive(): boolean; deactivate(): void; reactivate(): void; vatId(): string | null; update(input: Record): AuditChanges; toJSON(): Record; } /** * Single settlement of an open item. `money` is the settled * open-item amount INCLUDING the difference (api.md G2). * * `cause` says what closed the item, `difference` says by how much the amounts differed — the two * are independent: a payment can carry a discount, a cancellation never does. */ declare class Settlement { readonly entryId: Uuid; readonly money: Money; readonly settledAt: CalendarDate; readonly differenceMoney: Money | null; readonly differenceKind: SettlementDifferenceKind | null; readonly cause: SettlementCause; constructor(entryId: Uuid, money: Money, settledAt: CalendarDate, differenceMoney?: Money | null, differenceKind?: SettlementDifferenceKind | null, cause?: SettlementCause); toJSON(): Record; } /** * Open item (ledger-modell.md aggregate 5): arises from a posting to * an AR/AP account, references the origin posting + line. Invariant: * Σ settlements ≤ amount; partial settlements allowed. */ declare class OpenItem { readonly id: Uuid; readonly kind: OpenItemKind; readonly originEntryId: Uuid; readonly originLineIndex: number; readonly money: Money; readonly voucherId: Uuid; readonly openedAt: CalendarDate; readonly partnerId: Uuid | null; private readonly settlementList; constructor(id: Uuid, kind: OpenItemKind, originEntryId: Uuid, originLineIndex: number, money: Money, voucherId: Uuid, openedAt: CalendarDate, partnerId?: Uuid | null); /** * Restore from persistence: set already-validated settlements directly * (no re-check) — counterpart to PHP's `OpenItem::restore`. */ static restore(id: Uuid, kind: OpenItemKind, originEntryId: Uuid, originLineIndex: number, money: Money, voucherId: Uuid, openedAt: CalendarDate, partnerId: Uuid | null, settlements: Settlement[]): OpenItem; settlements(): Settlement[]; remaining(): Money; /** Remaining amount as of a cutoff date (null = today/all). */ remainingAt(asOf: CalendarDate | null): Money; status(): OpenItemStatus; statusAt(asOf: CalendarDate | null): OpenItemStatus; private settlementsUpTo; settle(settlement: Settlement): void; toJSON(): Record; } /** Account numbers unique per tenant — the adapter MUST guarantee that. */ interface AccountRepository { add(account: Account): void; save(account: Account): void; byNumber(number: AccountNumber): Account | null; byId(id: Uuid): Account | null; /** sorted by account number (codepoints) */ all(): Account[]; } interface FiscalYearRepository { add(fiscalYear: FiscalYear): void; save(fiscalYear: FiscalYear): void; byYear(year: number): FiscalYear | null; forDate(date: CalendarDate): FiscalYear | null; /** sorted by year */ all(): FiscalYear[]; } /** * Journal: append-only, gapless sequenceNumber per fiscal year. `save` * persists status changes; the entry itself is never deleted. */ interface JournalRepository { append(entry: JournalEntry): void; save(entry: JournalEntry): void; byId(id: Uuid): JournalEntry | null; nextSequenceNumber(fiscalYear: number): number; /** sorted by (fiscalYear, sequenceNumber) */ all(): JournalEntry[]; /** sorted by sequenceNumber */ forFiscalYear(fiscalYear: number): JournalEntry[]; } interface VoucherRepository { add(voucher: Voucher): void; byId(id: Uuid): Voucher | null; /** sorted by ID */ all(): Voucher[]; } interface OpenItemRepository { add(item: OpenItem): void; save(item: OpenItem): void; byId(id: Uuid): OpenItem | null; /** items that arose from this posting */ byOriginEntry(entryId: Uuid): OpenItem[]; /** in creation order */ all(): OpenItem[]; } /** The audit trail is part of the format (datenformat.md v0.3): append-only. */ /** * Criteria for `AuditTrail.find` — AND-combined, an absent one filters nothing. * * `objectIds` is the set a page of postings needs; `from`/`to` are inclusive calendar dates on the * recording moment; an absent or negative `limit` means everything from the offset on, exactly as * on `journal`. */ interface AuditCriteria { readonly objectType?: string | null; readonly objectId?: string | null; readonly objectIds?: readonly string[] | null; readonly actor?: string | null; readonly action?: string | null; readonly from?: string | null; readonly to?: string | null; readonly offset?: number | null; readonly limit?: number | null; } interface AuditTrail { append(record: AuditRecord): void; /** in capture order */ all(): AuditRecord[]; /** * The part of the trail a question is about (SPEC-018). * * `all()` is honest and, past a certain size, the wrong tool: a trail is the fastest-growing table * in the system, and answering "what happened to this posting" by materialising ten years of * history to discard almost all of it makes the cost of a screen scale with the age of the books. * So the criteria travel to the store, which is the only place that can decline to read a row. * * `count` is the number of matches **before** paging, so a page header needs no second call. * Order stays capture order, which is the trail's own total order. */ find(criteria: AuditCriteria): { records: AuditRecord[]; count: number; }; /** * Erase the trail's records about one object, and answer how many went (F-CORE-040). * * The one hole in "the trail is append-only because no code path deletes from it", and it is * opened deliberately and narrowly. Where a record has to be removable at all — the master data * a jurisdiction's privacy rules reach, never the books, which every retention rule protects — * removing the record alone removes nothing: `createPartner`'s own audit entry still holds the * name and the address in `changes`, so the data would only move to where nobody looks. * * It is reachable from exactly one operation (`erasePartner`), which refuses while any voucher * or open item names the partner. Nothing in the bookkeeping path can call it: the journal, the * entries and the trail's records about them stay untouchable, which is what GoBD asks for. */ eraseFor(objectType: string, objectId: Uuid): number; } interface AssetRepository { add(asset: Asset): void; save(asset: Asset): void; byId(id: Uuid): Asset | null; /** in acquisition order */ all(): Asset[]; } /** * Costing runs (F-KLR-001/004). * * The one repository that was missing for years, and its absence was not neutral: the service kept * its runs in a private `Map`, so a released run — the thing the requirements say evaluations read — * was gone with the process that produced it. `all()` is what gives a period its next version * number, which is also why the version no longer restarts at 1 after a restart. */ interface CostingRunRepository { add(run: CostingRun): void; save(run: CostingRun): void; byId(id: Uuid): CostingRun | null; /** sorted by period, then version */ all(): CostingRun[]; } /** * Inventory valuations (F-CORE-050). * * The same shape as `CostingRunRepository`, and for the same reason it exists at all: a valuation * that lives in the process that made it cannot be read back, and the record of *how* a stock * figure was reached is precisely what an inventory has to be able to show. `all()` is sorted * because the next version of a period comes out of the store, not out of a counter. */ interface InventoryValuationRepository { /** * Deliberately no `byId` and no `save`. A valuation is one act that never changes, and nothing * asks for a single one: the projection reports them all and the version counter reads them all. * An interface method nobody calls is a burden on every adapter author for a convenience the core * does not have — and it is the kind of thing that reads as "supported" long after it stopped * being exercised. */ add(valuation: InventoryValuation): void; /** sorted by fiscal year, then period, then version */ all(): InventoryValuation[]; } /** * Provisions (F-CORE-051). * * Shaped like `AssetRepository`, because a provision is the same kind of thing an asset is: a record * with a life, whose movements matter as much as its balance. `save` exists here and not on the * inventory port for exactly that reason — a valuation is one act and never changes, a provision is * used, released and re-measured over years. */ /** * Prepaid and deferred items (F-CORE-053). * * Deliberately no `byId`: the release run reads them all and the register reports them all; nothing * in the core asks for a single deferral. An interface method nobody calls is a burden on every * adapter author for a convenience the core does not have. */ interface DeferralRepository { add(deferral: Deferral): void; save(deferral: Deferral): void; /** in the order they were recognised */ all(): Deferral[]; } interface ProvisionRepository { add(provision: Provision): void; save(provision: Provision): void; byId(id: Uuid): Provision | null; /** in the order they were recognised */ all(): Provision[]; } /** * What a tenant *is*, apart from its books (SPEC-015). * * Everything else in this file persists a record the books are made of. This one persists the * tenant itself — its identity and the configuration it was set up with — and it exists because * that configuration had no owner at all: the tax profile, the dimension registry, the allocation * scheme and the imported mappings were constructor arguments rebuilt from whatever the caller * passed on every open. Five operations changed them, wrote a durable audit record about it, and * lost the change with the process. * * The chart of accounts settles the question of whether this is the library's business: it is * seeded from the pack, stored per tenant, changed by operations and read back by a projection, * and nobody ever argued it belonged to the embedding. A cost centre is master data the same way * an account is. * * Scoped to one tenant like every other repository here, so `load` needs no argument. Listing the * tenants in a store is deliberately NOT part of this port: a repository here answers for the * tenant it was built for, and "which tenants exist" is a question about the store, which the * adapter answers (`listTenants`). */ interface TenantRecordRepository { load(): TenantRecordData | null; save(record: TenantRecordData): void; } /** * The stored form. Deliberately raw data rather than the built objects: what goes in is exactly * what `TaxProfile.fromData`, `DimensionRegistry.fromData`, `setAllocationScheme` and * `importMapping` accept, so the round trip is symmetric by construction and no second serializer * can drift from the first one. */ interface TenantRecordData { id: string; name: string; baseCurrency: string; packIdentity: { id: string; version: string; } | null; config: { taxProfile: Record | null; dimensionTypes: Array<{ code: string; }>; dimensionValues: Array<{ typeCode: string; code: string; }>; allocationScheme: Record | null; mappings: Record[]; /** What the entity IS — legal form and, where a jurisdiction knows one, size class (F-CORE-039). */ entityProfile: { legalForm: string; sizeClass: string | null; } | null; }; } interface PartnerRepository { add(partner: Partner): void; save(partner: Partner): void; byId(id: Uuid): Partner | null; /** sorted by name, then ID */ all(): Partner[]; /** * Remove a partner outright (F-CORE-040) — the only repository in the core that can forget. * * Guarded by `PartnerService.erase`, never called from the bookkeeping path. See * `AuditTrail.eraseFor` for why the capability exists at all. */ remove(id: Uuid): void; } /** * The criteria of `AuditTrail.find`, applied in memory (SPEC-018). * * One place, because the rule has to be the same wherever it runs: the in-memory adapter filters * with this, a database adapter filters with SQL, and the two must not answer differently. An * adapter suite in each language drives the same criteria through both and compares. * * The one asymmetry worth naming: a store can decline to *read* a row, and this cannot. That is the * whole point of the port method and the reason this is a fallback rather than the implementation. */ declare function applyAuditCriteria(records: readonly AuditRecord[], criteria: AuditCriteria): { records: AuditRecord[]; count: number; }; /** * Postings still in status `entered` at a reference date (F-CORE-027). * * GoBD asks for finalization "at the latest with the VAT return" (Rz. 47 ff.) — a deadline * nothing in the data enforces, because it is a *German* rule and the substrate is * jurisdiction-free. What the substrate can do is make the deadline observable: the journal * already carries `entryDate` and `recordedAt`, so how long a posting has been sitting open * is a fold over the journal, not new state. * * The age is measured from `entryDate` (the bookkeeping date, zoneless) against `asOf`, not * from `recordedAt` — a posting recorded late for an old date is exactly the case the * deadline is about, and measuring from the recording moment would hide it. * * The projection reports; it never blocks. Which age is too old, and what happens then, is * the embedding application's workflow — the library supplies the number. * * Each row carries `actor` (F-CORE-037): who recorded the posting. This is the projection a * separation-of-duties check reads — "nobody may finalize a batch containing their own postings" — * and without the author it was the one question it could not ask here, so an application read the * whole audit trail per finalization and rebuilt the mapping itself. See `entryAuthors`. */ declare class UnfinalizedEntriesProjection { private readonly journal; private readonly clock; /** * Where the author of a posting lives — the entry itself does not carry one. * * Required, not optional-with-a-default. An optional dependency is how three services in the * database factory lost their audit writer and nobody noticed for a release: nothing fails to * compile, nothing warns, the output is merely poorer in one setup. */ private readonly audit; constructor(journal: JournalRepository, clock: Clock, /** * Where the author of a posting lives — the entry itself does not carry one. * * Required, not optional-with-a-default. An optional dependency is how three services in the * database factory lost their audit writer and nobody noticed for a release: nothing fails to * compile, nothing warns, the output is merely poorer in one setup. */ audit: AuditTrail); compute(params: Record): Record; } /** * Technical system description (F-IO-007) — the building block of a Verfahrensdokumentation * the *package* can supply (GoBD Rz. 151 ff.). * * The Verfahrensdokumentation has four parts: general description, user documentation, * technical system documentation, operating documentation. Three of them describe *your* * installation and processes and no library can write them. The technical one is different: * what the engine enforces, which operations exist, how the journal behaves and what the * stored format looks like are facts about the software, and stating them by hand means * stating them wrong within a release. * * So this projection reports them from the same constants the engine runs on. It is * deliberately **not** the whole document — it is the section an auditor would otherwise ask * the vendor for, in a form you paste into yours. * * Deterministic and tenant-independent apart from the identity block: same version of * summae, same description. * * `pack` names the manifest the tenant was composed from, or is null when it was built from * an inline rule bundle — in which case there is no manifest to name and a * Verfahrensdokumentation has to state the rule source by hand. */ /** * Every operation and projection the dispatcher answers. This is the list the description * publishes; `tenant-operations-contract.test.ts` holds its own literal copy and asserts * both agree, so a case dropped from the dispatcher and a name dropped from here cannot * cancel each other out. * * The test asks **both** directions, and the second one was missing for a long time: every * published name resolves to a handler, *and* every routed name is published. Seven finished, * documented, fixture-covered capabilities were routed and unpublished — an embedding app * validating its calls against this list could not call them at all, which is how it was * found. A surface larger than its declaration is the same defect as one smaller than it. */ declare const API_OPERATIONS: readonly ["acquireAsset", "allocate", "appropriateResult", "bookSpecialDepreciation", "closeFiscalYear", "closePeriod", "correct", "createAccount", "createFiscalYear", "createPartner", "createVoucher", "adjustInputTax", "deactivatePartner", "defineDimensionType", "defineDimensionValue", "disposeAsset", "erasePartner", "expandTax", "finalize", "importChartOfAccounts", "importMapping", "lockAccount", "post", "postVoucher", "reactivatePartner", "releaseCosting", "reopenPeriod", "reportAssetUsage", "recognizeDeferral", "recognizeProvision", "releaseProvision", "remeasureProvision", "reverse", "runCosting", "runDeferralRelease", "runDepreciation", "setAllocationScheme", "setEntityProfile", "setTaxProfile", "useProvision", "valuateInventory", "settle", "unlockAccount", "updatePartner", "writeDownAsset", "writeUpAsset"]; declare const API_PROJECTIONS: readonly ["accountSheet", "accounts", "assetRegister", "assetSchedule", "auditDataExport", "auditLog", "auditTrailIntegrity", "balanceSheet", "cashBasisReport", "cashJournal", "costAllocationSheet", "costingRuns", "datevExport", "deferralRegister", "duplicateVouchers", "ecSalesList", "fiscalYears", "gdpduExport", "incomeStatement", "inventoryValuation", "journal", "journalExport", "measurementConsistency", "openItems", "personalDataDescription", "overheadRates", "productionCost", "provisionRegister", "systemDescription", "tenantConfiguration", "trialBalance", "unappropriatedResult", "unfinalizedEntries", "vatReturn"]; /** What the audit trail records, so a reader can tell completeness from a sample. */ /** * Exported because `audit-trail-contract.test.ts` holds it against the pairs the operations * *actually* write, in both directions — the same guard `capabilities` has. It used to be a * hand-kept literal that nothing compared to reality, and it had already fallen behind once * (0.11.0, where it under-reported what the trail records). A list published to an auditor is a * claim, and a claim without a test is the thing this project calls a gate gap. */ declare const AUDITED_EVENTS: ReadonlyArray<{ objectType: string; actions: readonly string[]; }>; declare class SystemDescriptionProjection { private readonly tenantId; private readonly tenantName; private readonly baseCurrency; /** Null when the tenant was composed from an inline bundle: there is no manifest to name. */ private readonly packIdentity; /** * The tax profile the engine is actually running on (F-TAX-003). * * Nothing reported it, so an embedding could display only what it had written itself — the same * value by construction *in that embedding*, which is a property of one caller and not a * guarantee. It belongs here rather than in a projection of its own: this is the projection that * already describes the tenant, and a Verfahrensdokumentation has to state which taxation method * the books were kept under. */ private readonly taxProfile; /** * What the embedding declares about the identity behind `actor` (SPEC-020, reported by an * embedding app as its F-30). * * summae is handed an actor string and cannot know where it came from — that is what * `auditTrail.actorIsAuthenticated: false` has always said, and it stays true. The trouble was * what the field gets *used for*: an application generating the technical part of a * Verfahrensdokumentation put it in as "Urheber geprüft: **nein**", and then grew a login. The * document went on telling an auditor that every entry's author is unverified about an * installation where a password had been proved before the actor was ever set. * * So the only party that can know says so, and summae reports it **as a declaration** rather * than as a finding of its own. `null` — the default, and what every embedding gets until it * speaks — means nothing was declared, which is not the same as "no". * * Deliberately **not** stored with the tenant. This describes the running installation, not the * books: an embedding that drops its login tomorrow must not leave yesterday's claim behind in * a record. It is passed on every construction, like the pack. */ private readonly actorAuthentication; constructor(tenantId: Uuid, tenantName: string, baseCurrency: Currency, /** Null when the tenant was composed from an inline bundle: there is no manifest to name. */ packIdentity?: { id: string; version: string; } | null, /** * The tax profile the engine is actually running on (F-TAX-003). * * Nothing reported it, so an embedding could display only what it had written itself — the same * value by construction *in that embedding*, which is a property of one caller and not a * guarantee. It belongs here rather than in a projection of its own: this is the projection that * already describes the tenant, and a Verfahrensdokumentation has to state which taxation method * the books were kept under. */ taxProfile?: Record | null, /** * What the embedding declares about the identity behind `actor` (SPEC-020, reported by an * embedding app as its F-30). * * summae is handed an actor string and cannot know where it came from — that is what * `auditTrail.actorIsAuthenticated: false` has always said, and it stays true. The trouble was * what the field gets *used for*: an application generating the technical part of a * Verfahrensdokumentation put it in as "Urheber geprüft: **nein**", and then grew a login. The * document went on telling an auditor that every entry's author is unverified about an * installation where a password had been proved before the actor was ever set. * * So the only party that can know says so, and summae reports it **as a declaration** rather * than as a finding of its own. `null` — the default, and what every embedding gets until it * speaks — means nothing was declared, which is not the same as "no". * * Deliberately **not** stored with the tenant. This describes the running installation, not the * books: an embedding that drops its login tomorrow must not leave yesterday's claim behind in * a record. It is passed on every construction, like the pack. */ actorAuthentication?: { declared: boolean; method: string | null; } | null); compute(_params: Record): Record; } interface AccountRange { readonly from: string; readonly to: string; } /** * The conditions a rule may be keyed on. **Closed**, and the two that are absent are absent for * stated reasons (`docs/proposals/constraint-vocabulary.md`): `smallBusiness` is time-segmented and * would need a per-posting-date evaluation for one rule that only catches hand-postings, and an * amount condition would restate a threshold that already has an owner in the depreciation module — * a second source of truth for a number is worse than no rule. */ declare const CONDITION_KEYS: readonly ["legalForm", "taxationMethod"]; type ConditionKey = (typeof CONDITION_KEYS)[number]; type AppliesWhen = { readonly [K in ConditionKey]?: readonly string[]; }; interface ConstraintContext { readonly legalForm: string | null; readonly taxationMethod: string | null; } interface AccountCombinationRuleData { readonly whenAccountIn: AccountRange; readonly requireAccountIn?: AccountRange; readonly forbidAccountIn?: AccountRange; readonly appliesWhen?: AppliesWhen; readonly note?: string; } interface AccountUsageRuleData { readonly forbidAccountIn: AccountRange; readonly appliesWhen?: AppliesWhen; readonly note?: string; } /** * The constraint socket's account-facing predicates (F-CORE-042, F-CORE-047). * * **Why a second predicate mattered more than the rule it carries.** The constraint kind — the one * of the three policy kinds that exists to let a jurisdiction say *no* — could express exactly one * thought: *this account may not be posted without that dimension*. Every other prohibition a * jurisdiction has had to go somewhere else or nowhere, and `docs/gobd-conformance.md` §14 item 6 * has said so since the socket was built: the shape is settled, the vocabulary is not. A socket * with one predicate is not obviously a socket; it might be a feature with a data file. The second * one is what makes the first a vocabulary. * * **Three words now.** * - `accountCombinationRules` — an entry that touches an account in `whenAccountIn` must also touch * one in `requireAccountIn`, or must not touch one in `forbidAccountIn`. Exactly one of the two * per rule; a rule that said both would be two rules wearing one name. * - `accountUsageRules` — an entry must not touch an account in `forbidAccountIn` **at all**. Not a * combination and deliberately not expressed as one: see below. * - `appliesWhen` — either kind of rule may be conditioned on a **closed** set of tenant facts * (`legalForm`, `taxationMethod`). Closed on purpose: the moment conditions become an expression * language, a pack carries logic, and the whole point of the pack/substrate split is gone. * * **Why "the entry", not "the other side".** The named case is a granted discount that has to carry * its tax correction (the app-obligation list calls it A-13), and there both lines sit on the * *same* side — the discount is a debit and so is the VAT correction, with the receivable on the * credit. A predicate about sides would have missed the case it was built for. "Somewhere in the * same entry" is also the weaker claim, and the weaker claim is the one a pack can reason about * without knowing how an application splits its lines. * * **Why usage rules are their own word and not `forbidAccountIn: 0000–9999`.** That trick was the * obvious way to say "this account may not be used" with the vocabulary that already existed, and * it is wrong twice. It reads as a range, so the next author has to work out that the range is * meant to cover everything; and account numbers compare by **code point**, so `0000`–`9999` * silently fails to cover a chart whose numbers start with a letter and covers a six-digit chart * only by accident. A prohibition whose correctness depends on how a foreign chart happens to * number its accounts is not a prohibition. * * **What all of this deliberately cannot do.** It sees one entry. It cannot say "within ten days", * cannot reach across entries, and cannot require that a *settlement* be accompanied by anything — * `settle` records an allocation and posts nothing, so there is no entry there to constrain. A-13 * is reached through the posting the application makes for the discount, which is where the books * actually change; the settlement itself stays unconstrained and `docs/gobd-conformance.md` still * says so. */ declare class AccountCombinationRegistry { private readonly rules; private readonly usageRules; private constructor(); static empty(): AccountCombinationRegistry; static fromData(rules: readonly AccountCombinationRuleData[], usageRules?: readonly AccountUsageRuleData[]): AccountCombinationRegistry; /** What is in force, for `tenantConfiguration` — the same reason the dimension rules are readable. */ rulesInForce(): AccountCombinationRuleData[]; usageRulesInForce(): AccountUsageRuleData[]; /** * Checked over the whole entry, once, after the lines resolve — a per-line hook could not see the * combination, which is the entire subject. */ validateEntry(accounts: readonly AccountNumber[], context?: ConstraintContext): void; } interface DimensionTypeData { readonly code: string; } interface DimensionValueData { readonly typeCode: string; readonly code: string; } interface DimensionRuleData { readonly accountRange: { from: string; to: string; }; readonly requiredDimension: string; } /** * Dimension validation: mechanism in the core, contents as rule module data * (ledger-modell.md). Types/values are master data; mandatory dimensions come * from `ruleModules.dimensionRules`. */ declare class DimensionRegistry { private readonly types; private readonly values; private readonly rules; private constructor(); static empty(): DimensionRegistry; static fromData(dimensionTypes: DimensionTypeData[], dimensionValues: DimensionValueData[], dimensionRules: DimensionRuleData[]): DimensionRegistry; /** * The mandatory-dimension rules in force, in the shape they arrived in. * * Not part of `toData()` on purpose — that method answers what gets *stored*, and the rules are * the pack's, not the tenant's. This one answers what is *in force*, which is a different * question with a different reader: an embedding that offers a cost-centre field has to know on * which accounts leaving it empty will be refused, and until `tenantConfiguration` existed the * only way to find out was to post and read `E_DIMENSION_INVALID`. */ rulesData(): DimensionRuleData[]; /** * The same rules with different master data (SPEC-015). * * Reopening a tenant means combining two sources that are not the same kind of thing: the types * and values are the tenant's, and come back from its record; the rules — which accounts may not * be posted without a dimension — are the pack's, and come back from the pack. This is what keeps * them apart without asking the adapter to know the difference. */ withMasterData(dimensionTypes: DimensionTypeData[], dimensionValues: DimensionValueData[]): DimensionRegistry; /** * The registry as the data it was built from (SPEC-015) — `fromData(toData(r))` is `r`. * * Sorted, because this is what gets stored: two runs that declared the same types in a different * order must produce the same stored bytes, or the cross-test would compare a set against an * ordering accident. Rules are not included: which accounts require a dimension is the pack's * answer, not the tenant's, and it comes back from the pack on every open. */ toData(): { types: Array<{ code: string; }>; values: Array<{ typeCode: string; code: string; }>; }; /** * Declares a dimension type (a cost centre axis, a project axis, …). * * Dimension types and values are the tenant's own master data, like accounts — not the pack's, * because "Materialstelle" is a fact about one company and not about German law. They were * declarable only through the in-memory construction path, which meant a tenant built FROM A PACK * had an empty registry and every posting carrying a cost centre was rejected: cost accounting was * unreachable on `de`, `us` and `default` alike, and nothing in the packs said so. */ defineType(code: string): void; /** * Declares a value of an existing type. Refused for an unknown type rather than creating it on the * way: a typo in the type would otherwise open a second, near-identical axis in silence. */ defineValue(typeCode: string, code: string): void; validateLine(account: AccountNumber, dimensions: DimensionValue[]): void; } interface MappingLeaf { key: string; label: string; side: string | null; ranges: Array<{ from: string; to: string; }>; numbers: string[]; includeNonCash: boolean; includesNetIncome: boolean; parents: string[]; } /** Does a leaf match an account number? (Individual numbers or codepoint ranges.) */ declare function leafMatches(leaf: MappingLeaf, accountNumber: string): boolean; /** * Structure mapping (datenformat.md v0.2): assigns accounts to positions — * same structure for balance sheet, income statement, cash-basis lines and VAT-return reporting keys. */ declare class Mapping { readonly id: string; readonly kind: string; readonly version: string; readonly leaves: MappingLeaf[]; constructor(id: string, kind: string, version: string, leaves: MappingLeaf[]); static fromData(data: Record): Mapping; private static collectLeaves; leafFor(accountNumber: string): MappingLeaf | null; } /** Loaded mappings of a tenant (mutable via importMapping). */ declare class MappingRegistry { private readonly byIdMap; static empty(): MappingRegistry; static fromRuleModules(raw: unknown[]): MappingRegistry; add(mapping: Mapping): void; byId(id: string): Mapping | null; /** * Which mappings are in force, by name — what `tenantConfiguration` publishes. * * Identity only, never the positions. The definitions are the pack's, and the handbook rule that * keeps them there is deliberate: the embedding pins and ships the pack, summae takes it on every * open and stores no copy, because two answers to "which rules is this tenant on" is one answer * too many. Publishing the leaves here would create exactly that second answer. * * What a caller cannot know without this is the part that is genuinely summae's: which `mapping` * names `balanceSheet`, `incomeStatement` and `cashBasisReport` will accept — the pack's, plus * whatever `importMapping` layered on top or replaced. * * Sorted by id, so the answer does not depend on the order the registry was filled in. */ summaries(): Array<{ id: string; kind: string; version: string; }>; } /** * What a tenant is configured as (F-CORE-035) — the read side of the four things * `summae_tenants.config` holds. * * Since SPEC-015 the library stores a tenant's configuration: the tax profile, the dimension master * data, the allocation scheme and the imported mappings. Exactly one of the four was reported back * — the tax profile, through `systemDescription` — and the other three could be written and never * read. * * The reason that is worse than an ordinary gap is the seed rule that came with the same release. * Before it, an embedding passed its cost centres in on every open, so *its* copy was the truth by * construction. Now the stored record wins and what the caller passes is ignored from the second * open on: summae's copy is the truth, the embedding's is a guess, and nothing let it check. A * screen offering a cost-centre field had no way to ask which values the engine would accept except * to post and read `E_DIMENSION_INVALID`. * * **It reports what is in force, not what is stored**, and where the two differ the difference is * the point: * - `dimensionRules` and `accountCombinationRules` are the *pack's* — which accounts may not be * posted without which dimension, and which may not (or must) meet in one entry. * They are never stored (they come back from the pack on every open) and an embedding cannot * derive them, so a form cannot know which field it must not leave empty. * - `mappings` lists the pack's mappings *and* the imported ones. The record holds only the * imports, so mirroring it would answer "none" for a `de` tenant whose `balanceSheet`, * `incomeStatement` and `cashBasisReport` all work — the opposite of useful. * * Identity — id, name, base currency, pack — is deliberately not repeated here: that is * `systemDescription`'s block and it already reports all four. This projection answers the other * question, what the tenant was *set up* as. * * Deterministic: every list comes back sorted from the registry that owns it. * * The SAME shape lives in the PHP TenantConfigurationProjection. */ declare class TenantConfigurationProjection { private readonly taxProfile; private readonly dimensions; /** Raw, as `setAllocationScheme` accepts it. */ private readonly allocationScheme; private readonly mappings; /** * Which appropriation targets the pack offers. Same reason as `dimensionRules`: it is the * *pack's* answer, an embedding cannot derive it, and without it a screen offering "carry * forward / distribute" would have to find out by provoking E_APPROPRIATION_UNSUPPORTED. * Empty means the pack supports no appropriation at all. */ private readonly appropriationTargets; /** * What the entity IS, and what it could be. Same reason again: which legal forms exist is the * *pack's* answer, and the tenant's own form is stored library state an embedding cannot derive * — a screen offering "Rechtsform" would otherwise have to carry its own list and hope the two * agree. `entityProfile` is null until `setEntityProfile` has been called; `legalForms` is empty * for a pack that ships no catalogue. */ private readonly entityProfile; private readonly legalForms; private readonly sizeClasses; /** The constraint socket's second plug (F-CORE-042) — appended for the same reason it is last on `Tenant`. */ private readonly combinations; constructor(taxProfile: Record, dimensions: DimensionRegistry, /** Raw, as `setAllocationScheme` accepts it. */ allocationScheme: Record | null, mappings: MappingRegistry, /** * Which appropriation targets the pack offers. Same reason as `dimensionRules`: it is the * *pack's* answer, an embedding cannot derive it, and without it a screen offering "carry * forward / distribute" would have to find out by provoking E_APPROPRIATION_UNSUPPORTED. * Empty means the pack supports no appropriation at all. */ appropriationTargets: string[], /** * What the entity IS, and what it could be. Same reason again: which legal forms exist is the * *pack's* answer, and the tenant's own form is stored library state an embedding cannot derive * — a screen offering "Rechtsform" would otherwise have to carry its own list and hope the two * agree. `entityProfile` is null until `setEntityProfile` has been called; `legalForms` is empty * for a pack that ships no catalogue. */ entityProfile: { legalForm: string; sizeClass: string | null; } | null, legalForms: string[], sizeClasses: string[], /** The constraint socket's second plug (F-CORE-042) — appended for the same reason it is last on `Tenant`. */ combinations?: AccountCombinationRegistry); compute(_params: Record): Record; } /** * Cash journal — the separation of cash from non-cash transactions, and the cash-count check. * * GoBD Rz. 57 ff. asks for cash and non-cash transactions to be kept apart. In double-entry * that separation is already structural rather than a flag: a transaction *is* a cash * transaction exactly when it touches an account of subtype `cash`. So nothing has to be * marked and no field has to be added — what was missing is the view that presents the * separation, which is the form an auditor asks for (the cash account's sheet *is* the * Kassenbuch). * * The second half is the part that finds real defects: **a cash balance can never be * negative.** You cannot hold less than no cash — that is physics, not jurisdiction, which * is why it belongs in the substrate and not in a pack. A cash account that goes below zero * at any point means the books do not describe what was in the drawer, and it is one of the * first things a tax auditor looks for. The running balance is therefore checked at every * movement, not only at the end: a day that dips negative and recovers is exactly the case * a closing balance hides. * * Reports, never blocks. Whether a negative balance stops a workflow is the embedding * application's decision; the library supplies the finding. */ declare class CashJournalProjection { private readonly baseCurrency; private readonly accounts; private readonly journal; constructor(baseCurrency: Currency, accounts: AccountRepository, journal: JournalRepository); compute(params: Record): Record; } /** * Result of `post`: the posting plus the open items created along the way * (AR/AP automation — follows with the open-items slice, hence empty for now). */ declare class PostResult { readonly entry: JournalEntry; readonly openItemsCreated: unknown[]; constructor(entry: JournalEntry, openItemsCreated: unknown[]); } /** * How a taxable amount splits into base and tax — the **second seam** of the tax expansion * (F-TAX-010). * * **Why this is its own socket and not a fifth mechanism.** The mechanism seam covers *line * assembly*: it receives an already-computed, already-rounded tax amount and decides which accounts * and reporting keys it lands on. `core/src/CLAUDE.md` has said since 2026-08-16 that the variance * which actually differs between jurisdictions sits **before** that and had no socket at all — * `base × rate / 100` was written twice inside `TaxService`, once per rounding granularity, and a * pack could not reach it. Every tax system that quotes prices with the tax already inside was * therefore inexpressible, and that is most of them. * * **The two kinds, and what separates them.** * * - `net` — the amount handed in is the base. `tax = amount × rate / 100`. What summae has always * done, and the default when a tax code says nothing, so no shipped pack changes behaviour. * - `inclusive` — the amount handed in is the **gross**, tax already inside. * `tax = amount × rate / (100 + rate)`, and the base is what remains. Rounding happens once, on * the tax, and the base is derived by subtraction — the other order lets base and tax fail to add * up to the amount the caller actually posted, which is the one property an inclusive régime * cannot give up: the gross is a fact, the split is arithmetic. * * **What this seam deliberately does not reach**, because a socket's limits are part of its * contract. A *compound* base (Canadian PST computed on a GST-inclusive amount) needs the result of * another code and therefore an ordering between codes, which this function cannot see — it is * handed one amount and one rate. Tax at payment time (withholding, split payment) is not a base * question at all but a timing one, and margin schemes need the purchase price of the thing sold, * which is not in the posting. Those stay named and unbuilt; the repertoire question * (`core/src/CLAUDE.md`, "what would reopen it") is *not* settled by this change, because a * mechanism still is not describable as pure data. */ type TaxBaseKind = 'net' | 'inclusive'; /** The declared repertoire, for the contract test and for `tenantConfiguration`. */ declare function allTaxBaseKinds(): readonly TaxBaseKind[]; /** * Rule version of a tax code with validity period. Contents are * rule-module data — code cites no statute. mechanism `reverse_charge`: * VAT and input-tax line at once; `intra_community_supply`: tax-free. */ declare class TaxCodeVersion { readonly validFrom: CalendarDate; readonly validTo: CalendarDate | null; readonly rate: string; readonly taxAccount: string; readonly reportingKey: string | null; readonly mechanism: string; readonly inputTaxAccount: string | null; readonly inputReportingKey: string | null; readonly baseReportingKey: string | null; /** * How the amount handed in splits into base and tax (F-TAX-010) — the expansion's second seam. * * `net` is the default and what every shipped pack means today: the amount IS the base. A pack * that quotes prices with the tax already inside says `inclusive`, and nothing else about the * code changes — same account, same reporting key, same mechanism. See `tax-bases.ts` for why * this is a socket of its own rather than a fifth mechanism. */ readonly taxBase: TaxBaseKind; constructor(validFrom: CalendarDate, validTo: CalendarDate | null, rate: string, taxAccount: string, reportingKey: string | null, mechanism?: string, inputTaxAccount?: string | null, inputReportingKey?: string | null, baseReportingKey?: string | null, /** * How the amount handed in splits into base and tax (F-TAX-010) — the expansion's second seam. * * `net` is the default and what every shipped pack means today: the amount IS the base. A pack * that quotes prices with the tax already inside says `inclusive`, and nothing else about the * code changes — same account, same reporting key, same mechanism. See `tax-bases.ts` for why * this is a socket of its own rather than a fifth mechanism. */ taxBase?: TaxBaseKind); coversDate(date: CalendarDate): boolean; } /** * Tax code (tax-modell.md aggregate 1): bundled tax case as a list of rule * versions. Version selection follows the voucher date. */ declare class TaxCode { readonly code: string; readonly versions: TaxCodeVersion[]; readonly datevBu: string | null; constructor(code: string, versions: TaxCodeVersion[], datevBu?: string | null); versionFor(date: CalendarDate): TaxCodeVersion; } declare class TaxCodeRegistry { private readonly codes; private constructor(); static empty(): TaxCodeRegistry; static fromData(data: Array>): TaxCodeRegistry; allVersions(): TaxCodeVersion[]; datevBuFor(code: string): string | null; get(code: string): TaxCode; versionFor(code: string, date: CalendarDate): TaxCodeVersion; } /** What a legal form obliges. `required: false` is an answer, not a gap. */ interface ResolutionRule { required: boolean; deadlineMonths: number | null; /** Which sentence of which statute the deadline comes from — the pack's words, never the core's. */ basis: string | null; /** Deadlines that differ by size class, where the pack grades entities by size at all. */ bySizeClass: Record; } /** * The legal forms a jurisdiction knows, and which one this tenant is (F-CORE-039). * * **Why a pack holds this at all.** `appropriateResult` books a resolution and could not say when * one is due, or whether one is due at all — and both answers differ by jurisdiction *and* by what * the entity is incorporated as. Some forms owe a resolution within a fixed number of months of the * year end, some owe one sooner when the entity is small, and some owe none at all because nobody * resolves anything. The mechanism here is only the arithmetic — a form, a number of months, a year * end, a date — and every number and every citation behind it is the pack's, carried through * untouched in `basis`. Nothing in this file knows a statute, and the guard that keeps it that way * is `no-jurisdiction-text`. * * **The split stays where it always is.** The pack declares the rule and this reports the date; who * gets reminded, and what happens when the date passes, is the embedding's workflow. summae states * what the data say, it does not chase anybody. * * **The tenant's own form is stored, not derived and not guessed.** There is no sensible default — * a pack cannot know what its user incorporated as — so a tenant that has not said reports `null` * everywhere rather than the most common form, and `setEntityProfile` is what says it. The size * class is optional for the same reason: where a jurisdiction grades entities by size it does so on * measures the books only partly hold (headcount, for one), so it is declared, not computed. * * The SAME shape lives in the PHP LegalFormRegistry. */ declare class LegalFormRegistry { static empty(): LegalFormRegistry; private declaredForm; private declaredSizeClass; private forms; private sizeClasses; /** * Reads the `legalForms` plug out of the resolved bundle, the way every other plug arrives — the * tenant is built first and the pack is handed to it afterwards. A pack without the module leaves * the catalogue empty, which is a legitimate answer for a jurisdiction-free one and reported as * such rather than as a defect. */ setRuleModule(ruleModules: Record): void; /** Which forms this tenant may declare, sorted — the pack's answer, and part of every refusal. */ offered(): string[]; offeredSizeClasses(): string[]; /** * Sets what this tenant is. Refused by name against the pack's catalogue rather than accepted and * ignored: a misspelt form would otherwise report "no resolution required" for a GmbH, which is * the one wrong answer that looks like a right one. */ set(legalForm: unknown, sizeClass: unknown): { legalForm: string; sizeClass: string | null; }; /** * Puts back what was stored, without checking it against the catalogue. * * Deliberately lenient where `set` is strict: the books outlive a pack version, and a pack that * drops or renames a form must not make an existing tenant unopenable. The rule stops applying — * `resolution()` finds nothing and the projection reports `null` — which is the honest answer and * visible, rather than an open that fails with an error nobody can act on. */ restore(data: Record | null): void; declared(): { legalForm: string; sizeClass: string | null; } | null; label(): string | null; /** What this tenant's form obliges, or `null` when nothing is declared or the pack lost the form. */ resolution(): ResolutionRule | null; /** The deadline in months for this tenant, size class taken into account. */ deadlineMonths(): number | null; /** * When a resolution about a year ending on `fiscalYearEnd` is due. * * The end of the nth month after the year end, not the same day n months later. Deadlines of this * kind are written as "within the first n months of the following financial year", so a year * ending 30 November with eight months has until 31 July — a day later than plain month * arithmetic would say, and the wrong side of a deadline to be wrong on. */ resolutionDueBy(fiscalYearEnd: CalendarDate): CalendarDate | null; } /** * `setEntityProfile` — the write side of the registry (F-CORE-039). * * Three steps in a fixed order, the same order every configuration operation here uses: the * registry refuses first, the audit record is written second, the store third. A rejected call * therefore leaves no trail and no record — which is the discipline SPEC-015 had to invent after * five operations audited changes the books stopped carrying at the next restart. * * The SAME shape lives in the PHP EntityProfileService. */ declare class EntityProfileService { private readonly registry; private readonly audit; private readonly tenantId; private readonly configStore; constructor(registry: LegalFormRegistry, audit: AuditWriter, tenantId: Uuid, configStore: TenantConfigStore | null); set(input: Record): Record; } /** * Tenant tax profile (tax-modell.md aggregate 2): taxation method, * small-business status with validity period (mid-year change, * SF-11), VAT filing period. */ declare class TaxProfile { private readonly method; private smallBusiness; private readonly period; private constructor(); /** * The two documented sets, refused rather than approximated (F-TAX-003). * * `taxationMethod` used to be `=== 'cash' ? 'cash' : 'accrual'` and `vatPeriod` * `=== 'monthly' ? 'monthly' : 'quarterly'`, so a typo, a `null` or an object all arrived as a * valid-looking profile that books differently, and nothing said so. This is not a value object * built from a trusted internal source: `fromData` is fed from an embedding's configuration file, * which is exactly where a typo lives — and the value it decides is whether VAT falls due on * invoice or on payment. * * **The two sets are not the same kind of thing, and only one of them is safely here.** * `taxationMethod` is substrate mechanism: accrual and cash are the two ways this engine can time * a tax liability, and it implements both. `vatPeriod` is a *label* — it records which window a * tenant files in and selects nothing (`vatReturn` takes its own window). Which filing windows * exist is a question jurisdictions answer differently, so a closed list of them in the substrate * is a claim the substrate has no business making — **which is why the pack answers it now** * (SPEC-016): `packPolicy.vatPeriods` declares the windows a jurisdiction files in, and a pack * that declares them overrides the substrate's list entirely. */ /** * The two ways this engine can recognise revenue. Published because the pack resolver checks * `appliesWhen.taxationMethod` against it (I10) — a private list could only be checked by * copying it, and a copied repertoire is a repertoire that drifts. */ static readonly METHODS: string[]; /** * The filing windows assumed when a pack declares none — a **default, not a definition** * (SPEC-016). `packPolicy.vatPeriods` overrides it entirely, which is what lets a jurisdiction * that files bi-monthly say so without the substrate learning the word. */ private static readonly PERIODS; /** * @param vatPeriods what the pack recognises; absent = the substrate default (SPEC-016). Which * filing windows exist is a question jurisdictions answer differently, so a closed list of them * in the substrate is a claim the substrate has no business making — the pack answers it now, and * a pack that declares nothing keeps exactly the behaviour it had. */ static fromData(data: Record, vatPeriods?: readonly string[] | null): TaxProfile; /** * A profile summae itself stored, rebuilt without re-validating it. * * Validation belongs at the boundary, and this is not one: these values were checked when they * arrived. Re-checking them on the way *out* of our own store would mean a tenant whose pack * later drops a filing window can no longer be opened — a rule change reaching backwards into * books that were kept correctly under the old one. */ static restore(data: Record): TaxProfile; /** Absent keeps the documented default; anything else must be one of the documented values. */ private static oneOf; static default(): TaxProfile; taxationMethod(): string; isCashBasis(): boolean; vatPeriod(): string; smallBusinessAt(date: CalendarDate): boolean; setSmallBusiness(validFrom: CalendarDate, value: boolean): void; private static sorted; toJSON(): Record; } /** * Domain Service `post` and relatives (ledger-modell.md). Check order when * posting is part of the contract (api.md): * 1. Structure (E_ENTRY_TOO_FEW_LINES, E_ENTRY_INVALID_AMOUNT) * 2. References (E_ENTRY_NO_VOUCHER, E_VOUCHER_UNKNOWN, E_ACCOUNT_UNKNOWN, * E_ACCOUNT_LOCKED, E_DIMENSION_INVALID) * 3. Balance equation (E_ENTRY_UNBALANCED) * 4. Temporal context (E_PERIOD_UNKNOWN, E_PERIOD_CLOSED) * Only the first error is reported. * * `Ledger` keeps the operations that write postings — `post`, `correct`, `finalize`, `reverse` — * together with the line parsing they share, and is a thin facade for the three areas that were * only ever neighbours of the journal, not part of it: settlement (`SettlementService`), chart of * accounts (`ChartAdminService`) and fiscal years/periods (`FiscalPeriodService`). The facade is * deliberate: `TenantOperations` and every adapter keep talking to one object, so the split is a * change of shape inside the core and of nothing else. */ declare class Ledger { private readonly baseCurrency; private readonly accounts; private readonly fiscalYears; private readonly vouchers; private readonly journal; private readonly openItems; private readonly dimensions; private readonly ids; private readonly taxCodes; /** The constraint socket's second predicate (F-CORE-042), checked over the whole entry. */ private readonly combinations; /** * The two tenant facts a constraint rule may be conditioned on (`appliesWhen`, F-CORE-047). * Held as the live registries rather than as copied values, because the legal form is set by * `setEntityProfile` **after** the ledger exists — a snapshot taken here would leave every * conditional rule dormant for the life of the tenant. */ private readonly legalForms; private readonly taxProfile; private readonly auditWriter; private readonly settlements; private readonly chart; private readonly periods; constructor(baseCurrency: Currency, accounts: AccountRepository, fiscalYears: FiscalYearRepository, vouchers: VoucherRepository, journal: JournalRepository, openItems: OpenItemRepository, audit: AuditTrail, dimensions: DimensionRegistry, clock: Clock, ids: IdGenerator, taxCodes?: TaxCodeRegistry, tenantId?: Uuid | null, /** Passed straight through to the chart service, which is where dimensions are declared. */ configStore?: TenantConfigStore | null, /** The constraint socket's second predicate (F-CORE-042), checked over the whole entry. */ combinations?: AccountCombinationRegistry, /** * The two tenant facts a constraint rule may be conditioned on (`appliesWhen`, F-CORE-047). * Held as the live registries rather than as copied values, because the legal form is set by * `setEntityProfile` **after** the ledger exists — a snapshot taken here would leave every * conditional rule dormant for the life of the tenant. */ legalForms?: LegalFormRegistry | null, taxProfile?: TaxProfile | null); /** * The dimension registry this ledger validates against — so `tenantConfiguration` can report what * a posting will be measured by. Read-only access: declaring a type or a value goes through * `defineDimensionType`/`defineDimensionValue`, which audit and store the change. */ /** What `tenantConfiguration` reports, same reason as the dimension rules. */ /** * The tenant facts a conditional constraint rule is measured against (`appliesWhen`). * * Read fresh on every posting, never cached: `setEntityProfile` may run at any point in a * tenant's life, and a rule that only applies to tenants configured before the first posting * would be a rule nobody could rely on. Absent stays absent — an unset fact makes a rule keyed on * it dormant rather than failing the posting, which is argued where the matching is done. */ private constraintContext; combinationRegistry(): AccountCombinationRegistry; dimensionRegistry(): DimensionRegistry; /** * What a posting's creation records (F-CORE-014). * * A creation is a change from nothing, written as `from: null` rather than as an empty diff — the * idiom vouchers, fiscal years and dimensions already used, and which postings did not, so the * published invariant held for some records and not for others. * * **The lines are deliberately not copied here.** A finalized entry cannot change and an entered * one records its own change under `corrected`, so the journal already holds what the entry is; * duplicating the lines would double the largest table in the system and create a second answer * to what the posting says. What the trail adds is the frame a reader cannot reconstruct: when it * was booked, against which voucher, under which text. */ private static entryCreationDiff; post(input: Record): PostResult; /** * AR/AP automation: debit on a receivable account → receivable, credit on a * payable account → payable. Reversal postings create no items. */ private createOpenItems; correct(input: Record): JournalEntry; finalize(input: Record): number; reverse(input: Record): JournalEntry; settle(input: Record): OpenItem[]; createAccount(input: Record): Account; defineDimensionType(input: Record): Record; defineDimensionValue(input: Record): Record; lockAccount(input: Record): Account; unlockAccount(input: Record): Account; importChartOfAccounts(input: Record): number; createFiscalYear(input: Record): FiscalYear; closePeriod(input: Record): { fiscalYear: number; period: number; status: string; }; reopenPeriod(input: Record): { fiscalYear: number; period: number; status: string; }; closeFiscalYear(input: Record): FiscalYear; private parseLine; private requireVoucher; private resolveLines; /** * An account may be posted to only inside its validity window (F-CORE-045). * * Judged against the ENTRY DATE, not against today: that is the whole difference from a lock. An * account retired at a year end keeps taking a late correction for December and refuses January, * which is what retiring an account actually means; a lock would refuse both. */ private assertAccountsValidOn; private assertBalanced; private openPeriodFor; } declare class InMemoryAccountRepository implements AccountRepository { private readonly byNumberMap; private readonly byIdMap; add(account: Account): void; save(_account: Account): void; byNumber(number: AccountNumber): Account | null; byId(id: Uuid): Account | null; all(): Account[]; } declare class InMemoryFiscalYearRepository implements FiscalYearRepository { private readonly byYearMap; add(fiscalYear: FiscalYear): void; save(_fiscalYear: FiscalYear): void; byYear(year: number): FiscalYear | null; forDate(date: CalendarDate): FiscalYear | null; all(): FiscalYear[]; } declare class InMemoryJournalRepository implements JournalRepository { private readonly entries; private readonly byIdMap; private readonly sequences; append(entry: JournalEntry): void; save(_entry: JournalEntry): void; byId(id: Uuid): JournalEntry | null; nextSequenceNumber(fiscalYear: number): number; all(): JournalEntry[]; forFiscalYear(fiscalYear: number): JournalEntry[]; } declare class InMemoryVoucherRepository implements VoucherRepository { private readonly byIdMap; add(voucher: Voucher): void; byId(id: Uuid): Voucher | null; all(): Voucher[]; } declare class InMemoryOpenItemRepository implements OpenItemRepository { private readonly items; private readonly byIdMap; add(item: OpenItem): void; save(_item: OpenItem): void; byId(id: Uuid): OpenItem | null; byOriginEntry(entryId: Uuid): OpenItem[]; all(): OpenItem[]; } declare class InMemoryAuditTrail implements AuditTrail { private readonly records; append(record: AuditRecord): void; all(): AuditRecord[]; eraseFor(objectType: string, objectId: Uuid): number; find(criteria: AuditCriteria): { records: AuditRecord[]; count: number; }; } declare class InMemoryPartnerRepository implements PartnerRepository { private readonly byIdMap; add(partner: Partner): void; save(_partner: Partner): void; byId(id: Uuid): Partner | null; remove(id: Uuid): void; all(): Partner[]; } /** * The tenant record, in memory — the fake behind `TenantRecordRepository`. * * It stores a copy rather than the object it was handed, so a caller that keeps mutating its own * record cannot change what "has been saved" retroactively. That matters here more than in the * other fakes: this is the repository whose whole point is that a change outlives the object that * made it, and a fake that shares the reference would prove nothing. */ declare class InMemoryTenantRecordRepository implements TenantRecordRepository { private stored; load(): TenantRecordData | null; save(record: TenantRecordData): void; } /** Costing runs, in memory — the fake behind `CostingRunRepository`. */ declare class InMemoryCostingRunRepository implements CostingRunRepository { private readonly byIdMap; add(run: CostingRun): void; save(_run: CostingRun): void; byId(id: Uuid): CostingRun | null; all(): CostingRun[]; } declare class InMemoryInventoryValuationRepository implements InventoryValuationRepository { private readonly items; add(valuation: InventoryValuation): void; all(): InventoryValuation[]; } declare class InMemoryDeferralRepository implements DeferralRepository { private readonly items; add(deferral: Deferral): void; save(_deferral: Deferral): void; all(): Deferral[]; } declare class InMemoryProvisionRepository implements ProvisionRepository { private readonly items; private readonly byIdMap; add(provision: Provision): void; save(_provision: Provision): void; byId(id: Uuid): Provision | null; all(): Provision[]; } declare class InMemoryAssetRepository implements AssetRepository { private readonly items; private readonly byIdMap; add(asset: Asset): void; save(_asset: Asset): void; byId(id: Uuid): Asset | null; all(): Asset[]; } /** * Asset subledger (assets-modell.md): low-value-asset switch at acquisition, * depreciation run idempotent per run target, postings through the ledger (finalized immediately). * Depreciation distribution: monthly values = allocate of the acquisition cost over the useful life (flat). */ declare class AssetService { private readonly baseCurrency; private readonly assets; private readonly fiscalYears; private readonly vouchers; private readonly ledger; private readonly ids; private readonly tenantId; private readonly audit; private ruleModule; constructor(baseCurrency: Currency, assets: AssetRepository, fiscalYears: FiscalYearRepository, vouchers: VoucherRepository, ledger: Ledger, ids: IdGenerator, tenantId?: Uuid | null, audit?: AuditWriter | null); private trace; setRuleModule(ruleModule: Record): void; acquire(input: Record): Record; dispose(input: Record): Record; /** * Reports what the asset has produced, and writes off what that use consumed. * * Where a jurisdiction allows it, an asset that wears by use rather than by time may be depreciated * by output — kilometres, operating hours, copies. There is nothing to plan: the number comes from * goods movements, meter readings, job cards, none of which are in the books. So the caller reports * the meter and the core does the arithmetic. * * The arithmetic is cumulative on purpose. Each report splits the acquisition cost between what the * asset has now given and what it has not, and books the difference against what is already written * off. Computing each period on its own would let rounding drift, and the last report would leave a * stray cent behind on an asset that is fully used up. This way the final report, the one that * reaches the total output, lands on the cost exactly. * * More output than expected is not an error — a lorry can outlive its estimate — but there is no * more cost to write off, so the booking is capped at the book value and the answer says so. */ reportAssetUsage(input: Record): Record; private usageVoucher; /** * Books part of an additional allowance (see `Asset.specialDepreciationBudget`). * * Two things make this an operation rather than a plan. The amount is the taxpayer's to choose — a * jurisdiction that grants "up to 40 % over five years" grants exactly that, and any split is as * valid as any other — and the entitlement itself is a question about the business (a profit limit, * a share of business use) that summae has no way to check and does not pretend to. So the caller * says how much and when, and the core enforces only what it can know: not more than the budget, * not outside the window, not on an asset that has none. */ bookSpecialDepreciation(input: Record): Record; private specialDepreciationVoucher; /** * Rate and window of the additional allowance, from the pack. Refused rather than defaulted, like * every other pack question here: a core that invents "40 % over five years" has one jurisdiction's * tax policy in the substrate. */ private specialDepreciationTerms; /** * An unplanned write-down — the value fell, and not because time passed. * * The planned schedule answers wear and tear; it has nothing to say about a machine damaged in March * or a building whose neighbourhood lost its factory. Where the loss is expected to last, writing * the asset down is not an option a preparer takes but an obligation, and until now the only ways to * express it were disposing of the asset (wrong — it still exists) or posting by hand past the asset * register (wrong — the register then disagrees with the ledger about what the asset is worth). * * A reason is required. An unplanned write-down that does not say why is not auditable, and "why" is * the whole difference between an impairment and a mistake. * * The remaining plan is rewritten: what is left after the write-down is spread over the plan months * still open. Leaving the plan alone would depreciate past zero; stopping the plan would finish the * asset early. Carrying the reduced value over the remaining life is what a lasting impairment means. */ writeDownAsset(input: Record): Record; /** * Reverse an earlier write-down when its reason has ceased (`writeUpAsset`, F-CORE-052). * * **This is a duty, not an option, and its absence was the smallest of the real legal gaps.** * `writeDownAsset` has existed for a long time with no counterpart, so an asset written down in a * bad year stayed down for ever — which understates equity and the result exactly as permanently * as the write-down was meant to state them prudently for one year. * * **Two caps, and the second is the one that needs the shadow plan.** Nothing may be written back * that was not written down (`unreversedWriteDowns`), and the book value may not exceed what it * would have been had the write-down never happened (`amortisedCostCeiling`). The second is * stricter than the first and is easy to get wrong: a write-down also lowers every remaining * planned instalment, so the book value climbs *above* the untouched plan as the years run on. * Reversing in full would then carry the asset over its amortised cost. * * **What the caller decides and what the core decides.** Whether the reason has ceased, and by how * much the value has recovered, is an appraisal — a judgement about the world, which no library * makes. The amount is therefore an input. The ceiling is arithmetic, and that is the part that is * enforced. */ writeUpAsset(input: Record): Record; /** * Where a write-up is booked. **Required from the pack**, and the asymmetry with the impairment * account is deliberate rather than an oversight. * * A write-down that names no account falls back to ordinary depreciation, which is not wrong — * only less informative, because both are a charge against the same asset. There is no equivalent * on the income side: the disposal-proceeds account is specifically a *gain on disposal*, and a * write-up is not one. Borrowing it would put a figure under a heading that says something untrue * about it, which is worse than asking the pack to say where it goes. */ private writeUpIncomeAccount; private writeUpVoucher; private rebaseAfterSpecialWindow; private writeDownVoucher; private requireVoucherId; /** * Where an unplanned write-down is booked. A pack that separates it from ordinary depreciation says * so; one that does not gets the depreciation account, which is what it had before this operation * existed and is not wrong, only less informative. */ private impairmentExpenseAccount; runDepreciation(input: Record): Record; requireAsset(assetId: unknown): Asset; private yearTarget; private monthTarget; private monthAmounts; private bookingDate; /** * Dimensions the asset carries, in the shape a posting line expects (IMPL-023). Every machine * entry about an asset gets them on every line: the whole event belongs to that cost centre, and * a line without them would be refused wherever the pack makes a dimension mandatory — which is * precisely the case that used to make depreciation impossible to run. */ private static parseDimensions; private withDimensions; private postMachineEntry; private depreciationVoucher; private resolveRoute; /** The threshold row in force on the acquisition date — the first whose validity window contains it. */ private applicableThreshold; /** * How long a pooled asset is written off. Refused rather than defaulted: a pack that opens a pool * range without saying over how long is incomplete, and picking a number here would put a statute * back into the core — the exact thing SPEC-004 is about. The schema requires the field alongside * `poolMax`, so this fires only for hand-fed rule data that never went through a pack. */ private poolYears; /** * Whether a disposal takes the item out of the pool. Same reasoning as `poolYears`, and the same * refusal: this is a jurisdiction's answer, not a property of pooling. Germany does not reduce * the pool when an item leaves (the yearly fraction runs to the end of the term regardless); * the UK and Australia take disposals out of their pools. Deciding it here would have put a * statute back into the core — which is exactly what IMPL-019 accidentally did before this. */ /** * Does the pool's first year get shortened by the acquisition month? * * Germany says no: the pool is dissolved in the fiscal year it is formed and the following ones by * equal fractions, so an asset bought in November still carries a full fraction in that year, and * the term ends after `poolYears` fiscal years. Treating a pool like ordinary linear depreciation — * pro rata from the month of acquisition — understated the first year and invented a last one. * * Other pool regimes answer this differently, which is why the pack has to say it rather than the * core assuming it — the same reason `poolYears` (SPEC-004) and `poolReducedOnDisposal` (IMPL-019) * are pack data. */ private poolProRataInFirstYear; /** * Where the depreciation plan starts. Capitalised assets start in the month of acquisition (pro rata * temporis). A pooled asset whose pack dissolves the pool in whole fiscal-year fractions starts at * the beginning of the fiscal year it was acquired in — that, and nothing else, is what makes the * first year full and the term end after `poolYears` years. */ private planStartFor; private poolReducedOnDisposal; /** A pooled asset that its pack keeps in the pool after disposal — the write-off does not apply. */ private staysInPool; private thresholds; /** * Which yearly bucket a plan month belongs to. * * The fiscal year that contains the month, when one is set up — NOT its calendar year. Where the * two differ the old shortcut came apart badly: for a fiscal year 07/2026–06/2027 (labelled by its * end year) an asset acquired in September 2026 had its September-to-December months filed under * "2026", a label no fiscal year carries, so no run ever booked them, while months belonging to the * following fiscal year were pulled into this one. Which months belong to a fiscal year is what a * fiscal year is; reading it off the calendar was simply wrong. * * Falls back to the calendar year for a month beyond every fiscal year that has been set up. That is * not a second opinion about the boundary — it keeps the weighting COMPLETE. The yearly amount comes * from allocating the cost across all buckets by month count, so dropping the months that reach past * the last configured year would push their share into the years that remain and write the asset off * too fast. With calendar-year fiscal years the fallback is identical to the answer above; with * deviating ones it is the old behaviour, and it corrects itself as soon as the year is created. The * honest limit: set up the fiscal years an asset runs through. */ private planMonthYear; /** * The depreciation method the caller asked for. Straight line when absent — the method every * jurisdiction allows and the only one this core knew until 2026-08-23. */ /** * Expected total output. A whole number, at least one — JSON has no int/float split, so 100000.0 is * the same value as 100000, but 100000.5 is a caller's mistake and not half a kilometre. */ private static parseTotalUnits; private static parseMethod; /** * A declining-balance plan, with the switch to straight line built in. * * Each year takes a fixed percentage of what is LEFT, so the amounts fall and never quite reach * zero on their own — which is why every declining-balance regime pairs the method with a switch to * straight line over the remaining life, and why the final year simply takes the remainder. Without * that last step an asset would keep a residue forever. * * The switch is taken automatically at the first year where straight line over the remaining life * yields more. It is a permission rather than a duty, but no one entitled to it declines it — an * option nobody ever sets differently is better expressed as the behaviour. * * Rate, factor and the window come from the pack: the mechanism is shared (Germany applies it both * to movables and, at a different rate, to new residential buildings), the numbers are not. Both the * percentage and the remaining-life figure go through allocate, so the cents fall where the rest of * the core puts them. */ private decliningBalanceSchedule; /** * The declining-balance rule in force on the acquisition date, for this kind of asset. Refused * rather than defaulted, like every other pack question here: a core that invents a rate has one * jurisdiction's tax policy in the substrate, and these rates are the most short-lived numbers in * the whole pack — Germany's current one applies to a window of two and a half years. * * Two entries can be in force at once, because a jurisdiction may run one declining-balance regime * for movables and another for buildings over overlapping windows — Germany does exactly that. So * an entry may name the asset classes it covers, and **a class-specific entry wins over a general * one no matter which comes first in the file.** Order-independence is the point: a rule that * changed meaning when someone appended a line above it would be a trap, and the general entry is * the one a pack author is most likely to add later. * * An entry without `assetClasses` covers every class, which is what a pack written before this * distinction existed says — those keep computing what they always did. */ private decliningBalanceRule; /** * A useful life given per acquisition: a whole number of months, at least one. JSON has no * int/float split, so 60.0 is the same value as 60 — but 60.4 is a caller's mistake and not a * number to round into shape. */ private static parseUsefulLifeMonths; private usefulLifeMonths; private counterAccount; private depreciationExpenseAccount; private gwgExpenseAccount; /** * Depreciation owed up to the disposal, booked before the write-off (IMPL-022). * * Without this the disposal wrote off whatever carrying amount happened to be booked, and the * asset's last months of depreciation never happened at all: `runDepreciation` skips disposed * assets, so nobody would book them afterwards either. The expense landed in the disposal * account as an inflated loss instead of in depreciation — the total hit the income statement * correctly, the split did not, and the depreciation figure the fixed-asset schedule reports * was short. * * Which months are owed follows the schedule's own due-date convention — a plan month falls due * on its last day, exactly as `monthTarget` reads it for the regular run. No new rule is * invented here, and deliberately so: whether the month an asset leaves in counts as a whole * month is a *jurisdiction's* answer (Germany grants it, US conventions are half-year or * mid-quarter), so it belongs in a pack, not in this code. Consequence today: an asset disposed * mid-month gets no depreciation for that month. Recorded as a follow-up. */ private catchUpDepreciation; private disposalProceedsAccount; private disposalLossAccount; /** * The disposal entry (F-AST-004). Two things leave the books at once: the asset's carrying * amount, and the difference between that and what the sale brought in. * * This core depreciates *net* — `runDepreciation` credits the asset account directly, there is * no accumulated-depreciation account — so the write-off is a single credit of the carrying * amount against that same account, not the gross form with an offsetting contra account. * * The difference is a gain (proceeds above book value) or a loss (below, including a scrapping * with no proceeds at all), and it goes to the account the pack names for it. Before this, * `dispose` booked only `bank → proceedsAccount`: the asset stayed in the balance sheet at its * carrying amount and the proceeds counted as income in full, overstating profit by exactly * that amount. * * The `proceedsAccount` input parameter still wins over the pack's account — it is documented * and fixtures pass it — but is no longer required for the entry to happen. */ private disposalLines; private assetAccount; private parseMoney; } /** * Stock: measurement and posting (F-CORE-050). * * **The link that was missing.** Cost accounting justifies itself through a chain — cost centres → * allocation → overhead rates → production cost → *inventory valuation* → balance sheet — and only * the last link makes it bookkeeping rather than controlling. The middle was built and good; both * ends were missing. This is the outer one: the production cost the costing run computes now * reaches an account, and that account now reaches a balance-sheet position. * * **Socket and plug, as everywhere else.** The core knows *that* stock is carried at the lower of * its cost and a value the caller supplies, that the difference against what the books already * carry is booked, and that the counter-account is whatever the pack names for that category. It * knows nothing about *which* accounts, which categories a jurisdiction distinguishes, or whether * a change in raw materials belongs with material expense while a change in finished goods is its * own line — all of that is the `inventory` module. * * **The division the costing service refuses to do.** `computeProductionCost` deliberately does not * divide by a quantity, on the ground that the core carries no quantities. That is still true: it * carries none. Here a quantity is *handed in*, together with the produced quantity the run's total * relates to, and the division happens where both are declared inputs of one call. Nothing is * stored as a stock and nothing is carried forward. * * **Lower of cost or market, and why the mechanism is jurisdiction-free while the duty is not.** * Given a `marketValue` per unit, the lower of it and the unit cost is used, and the row says so. * Whether comparing is a duty, an option or forbidden is the pack's business; the arithmetic of * "take the lower of two numbers and say which one you took" is not. A row that only showed its * result would be unauditable, exactly as a production cost showing only its total would be. */ declare class InventoryService { private readonly baseCurrency; private readonly accounts; private readonly journal; private readonly vouchers; private readonly runs; private readonly valuations; private readonly ledger; private readonly ids; private readonly tenantId; private readonly audit; private ruleModule; constructor(baseCurrency: Currency, accounts: AccountRepository, journal: JournalRepository, vouchers: VoucherRepository, runs: CostingRunRepository, valuations: InventoryValuationRepository, ledger: Ledger, ids: IdGenerator, ruleModule?: Record, tenantId?: Uuid | null, audit?: AuditWriter | null); setRuleModule(ruleModule: Record): void; /** Value the stock of one period and book the change (`valuateInventory`). */ valuate(input: Record): Record; /** What was valued, how, and out of what (`inventoryValuation`). */ valuationReport(params: Record): Record; private valuateCategory; /** Where a unit value comes from, and it is never guessed. */ private unitCost; /** * What the books already carry on this account, up to and including the valuation period. * * Cumulative across fiscal years, because a stock account is a balance-carrying account and * carries forward implicitly (F-CORE-021) — there is no opening entry to read instead. */ private carryingAmount; /** stock account -> change account */ private packCategories; private releasedRun; private requireCategories; private requireDate; /** * Strings only, exactly as Money takes strings only. A JSON number has already lost the argument * before it arrives: 0.1 is not 0.1, and a quantity that reads back differently in the two * languages breaks byte parity at the first export. */ private requireRawString; private requireDecimal; private optionalDecimal; private nextVersion; private postMachineEntry; } /** * Provisions: formation, use, release, re-measurement (F-CORE-051). * * **Why this is a duty and not a feature.** A provision is the one balance-sheet item a business * must recognise for something that has *not yet happened* — an obligation whose amount or timing * is uncertain. Leaving it out overstates the result and the equity, which is why the law makes it * mandatory rather than optional. summae had nothing: no account, no position, no operation, zero * occurrences in either core. A balance sheet without it is not merely incomplete, it is wrong in a * direction that flatters. * * **Four operations, because there are four events and they mean different things.** Recognising is * an expense for a future obligation. *Using* it is that obligation coming true. *Releasing* it is * the obligation going away — income the business never had to pay. *Re-measuring* is the estimate * moving while the obligation stands. A design with one "adjust" would net these into a number that * answers none of the questions an auditor asks, which is the same defect as reading a provision * off an account balance. * * **Socket and plug.** The core knows *that* a provision is recognised as an expense against a * liability account, that using it settles against something else, that releasing it is income, and * that a long-dated one is discounted. Which accounts, whether discounting applies at all and from * what remaining term — all pack. * * **The discount rate is deliberately not shipped, and that is a decision rather than a gap.** The * pack declares the *rule* (discount from a remaining term of n months, compounded annually) and * cites its basis; the *rate* arrives per act as an input. In Germany it is an average of the last * seven years' market rates, published **monthly** — a number that would be stale in a pack file * before anybody upgraded, and a stale legal rate that looks authoritative is worse than an absent * one. So a provision that must be discounted and carries no rate is refused, by name, rather than * recognised undiscounted. */ declare class ProvisionService { private readonly baseCurrency; private readonly accounts; private readonly vouchers; private readonly provisions; private readonly ledger; private readonly ids; private readonly audit; private ruleModule; constructor(baseCurrency: Currency, accounts: AccountRepository, vouchers: VoucherRepository, provisions: ProvisionRepository, ledger: Ledger, ids: IdGenerator, ruleModule?: Record, audit?: AuditWriter | null); setRuleModule(ruleModule: Record): void; /** Recognise a provision (`recognizeProvision`). */ recognize(input: Record): Record; /** The obligation came true (`useProvision`). */ use(input: Record): Record; /** The reason ceased (`releaseProvision`). */ release(input: Record): Record; /** The estimate moved while the obligation stands (`remeasureProvision`). */ remeasure(input: Record): Record; /** The register (`provisionRegister`). */ register(params: Record): Record; /** Discounting: mechanism here, everything jurisdictional in the pack. */ private discount; private declaredAccount; private packModule; require(provisionId: unknown): Provision; private trace; private post; private requireString; private requireExistingAccount; private requireDate; private optionalDate; private requireMoney; } /** * Prepaid and deferred items: recognition and the release schedule (F-CORE-053). * * **The accounts were never the gap.** The shipped German chart has carried both from the start and * both have had a balance-sheet position. What was missing is the *plan*. An insurance premium paid * in December for the following year could be deferred and then had to be released by hand, month * after month, from memory — which is exactly the failure `runDepreciation` exists to prevent for * arithmetic that is identical: an amount spread evenly over a known number of periods. Two * mechanisms that differ only in whether the machine remembers is not a design, it is an omission. * * **Two kinds, opposites rather than variants.** A *prepaid expense* is money already paid for a * service still to come, so it is an asset and its release is an expense. A *deferred income* is * money already received for a service still to be rendered, so it is a liability and its release is * revenue. Every posting here flips with the kind; nothing else does. * * **The release run mirrors the depreciation run, deliberately and down to the answer shape.** One * period at a time, idempotent because each deferral records which periods it has released, * `alreadyRun` where there was nothing left to do. Somebody who has closed a period with * `runDepreciation` should not have to learn a second vocabulary for the same act. * * **Socket and plug.** Which account holds each kind is the pack's; which expense or revenue the * amount belongs to is the caller's, because it is a fact about the transaction rather than about * the jurisdiction. */ declare class DeferralService { private readonly baseCurrency; private readonly accounts; private readonly fiscalYears; private readonly vouchers; private readonly deferrals; private readonly ledger; private readonly ids; private readonly tenantId; private readonly audit; private ruleModule; constructor(baseCurrency: Currency, accounts: AccountRepository, fiscalYears: FiscalYearRepository, vouchers: VoucherRepository, deferrals: DeferralRepository, ledger: Ledger, ids: IdGenerator, ruleModule?: Record, tenantId?: Uuid | null, audit?: AuditWriter | null); setRuleModule(ruleModule: Record): void; /** Defer an amount and fix its release plan (`recognizeDeferral`). */ recognize(input: Record): Record; /** * Release what a period owes, for every deferral (`runDeferralRelease`). * * The depreciation run's shape, on purpose: one period, idempotent, `alreadyRun` when there is * nothing left. A period that was already released books nothing a second time, because each * deferral records what it has released rather than deriving it from a balance. */ runRelease(input: Record): Record; /** What is deferred, over what, and how far it has run (`deferralRegister`). */ register(params: Record): Record; private periodAt; /** * How many periods a year has — read from the fiscal year where there is one, twelve otherwise. * * A release plan that ran off the end of a year has to know where the next one starts, and * assuming twelve for a tenant whose year is divided differently would put instalments in periods * that do not exist. */ private periodsPerYear; private periodEnd; private packAccount; private post; private requireString; private requireExistingAccount; private requireDate; private requireMoney; } /** Partner operations (api.md v0.4): createPartner / updatePartner with audit. */ declare class PartnerService { private readonly partners; private readonly audit; private readonly clock; private readonly ids; /** * The chart, so an account link can be checked against it (F-CORE-032). * * Not optional: a service built without it would validate nothing and say nothing, which is the * state this check was added to end. */ private readonly accounts; /** * The two places a partner is reachable from the books (F-CORE-040) — needed only by `erase`, * which must refuse while either still names it. * * A journal entry never names a partner directly; it reaches one through its voucher, which is * why the journal is not consulted here and why an entry cannot orphan a reference this check * would miss. */ private readonly vouchers; private readonly openItems; constructor(partners: PartnerRepository, audit: AuditTrail, clock: Clock, ids: IdGenerator, /** * The chart, so an account link can be checked against it (F-CORE-032). * * Not optional: a service built without it would validate nothing and say nothing, which is the * state this check was added to end. */ accounts: AccountRepository, /** * The two places a partner is reachable from the books (F-CORE-040) — needed only by `erase`, * which must refuse while either still names it. * * A journal entry never names a partner directly; it reaches one through its voucher, which is * why the journal is not consulted here and why an entry cannot orphan a reference this check * would miss. */ vouchers: VoucherRepository, openItems: OpenItemRepository); /** * A partner may only be linked to accounts the books actually carry. * * Without this a partner could be linked to 9999 in a chart that stops at 3110: the operation * succeeded, the link was stored as a list of strings on the aggregate, and nothing ever reported * it. That is master data wrong for every reader of the books, not only for the screen that * entered it — the same argument that pulled `name` and `kind` in here rather than leaving them * to the embedding. The account link was made updatable in that pass and its check was not. * * Whole-list semantics are untouched: an empty list still clears the link. */ private validateAccountNumbers; /** * A partner needs a name, and the kinds are the three the manual names (F-CORE-032). * * Both used to be optional in the widest sense: `name` defaulted to `""`, so a request that * forgot it created a nameless partner indistinguishable from the next one and impossible to * pick out of a list; `kind` was a plain string, so `custommer` was a partner kind like any * other and only surfaced as a category nothing could filter on. An embedding application ended * up validating both itself, which is the wrong place: master data that is wrong here is wrong * for every reader of the books, not just for the screen that entered it. */ private validateMasterData; create(input: Record): Partner; update(input: Record): Partner; /** * Marks a partner as no longer in use, and takes it back (F-CORE-034). * * Both directions in one place, like the account lock, because the audit record is the point of * the operation and two copies of it would be two chances for one direction to record less. */ setStatus(input: Record, target: 'active' | 'inactive'): Partner; /** * Erase a partner and the trail's records about it (F-CORE-040). * * **Why this exists next to `deactivate`, which reads like it should be enough.** It is not the * same question. `inactive` says *we no longer trade with them* and is a state the books keep; * erasure says *this record must not be here at all*. The mechanism is the same in every * jurisdiction and the reason is not: wherever a retention rule applies it applies to what the * books reference, and a partner the books have never referenced falls outside it. So the line * this operation draws — referenced or not — is the only line the core knows. Which rule puts a * record on which side of it, and under what name, is documented outside the core * (`docs/gdpr-conformance.md`) and never asserted here. * * **Why it also erases the audit records about the partner.** `createPartner` writes the name * and, if given, the address into `changes`. Removing the partner row while that record stands * erases nothing — the personal data simply moves to the place nobody looks. So the records * *about this partner* go with it, and a single new record is appended in their place naming the * id, the actor and the moment, and carrying **no personal payload**. The trail keeps the fact * that an erasure happened, which is what an audit asks of it, and stops keeping what the law * says must go. * * **What it will not touch.** A voucher or an open item naming the partner is a bookkeeping * record under retention, and the refusal is unconditional — `E_PARTNER_IN_USE`, with the counts, * so a caller can say *why* rather than only *no*. Nothing here can reach a journal entry. */ erase(input: Record): { id: string; erasedAuditRecords: number; }; require(partnerId: unknown): Partner; private recordAudit; } /** * EC sales list basis (v0.4, SF-21): intra-community supplies per VAT ID and * period — from reporting-key tags of the intra-community-supply codes, partner via the voucher. * * **A supply that cannot be reported is reported as unreportable** (F-IO-011). The list is keyed by * VAT ID, so a supply whose partner has none used to fall out of it silently: two postings, one * with a VAT ID and one without, and the answer was one row and nothing else. That is the dangerous * direction — in the jurisdictions that have this report, a supply without the recipient's * registration number is typically not exempt at all, so what * dropped out was exactly the case where something is wrong. Same shape as `vatReturn.gapWarnings`, * and for the same reason: the warning belongs at the figures, next to what is filed, rather than * in a projection of its own that whoever files may never open. * * Not a refusal. Whether a missing VAT ID makes a supply taxable is jurisdiction law and the * embedding's call; the library's job is to make sure the case is never invisible. */ declare class EcSalesListProjection { private readonly baseCurrency; private readonly journal; private readonly vouchers; private readonly partners; private readonly registry; constructor(baseCurrency: Currency, journal: JournalRepository, vouchers: VoucherRepository, partners: PartnerRepository, registry: TaxCodeRegistry); compute(params: Record): { rows: Array>; gapWarnings: Array>; }; } /** * Trial balance (SuSa) — columns binding (api.md v0.4): * - openingBalance: cumulative balance BEFORE the FY (0 for income accounts) * - debitTotal/creditTotal: turnover figures of the period (FY up to period) * - balance = openingBalance + debitTotal − creditTotal (debit balances positive) * Sorting: account number by codepoints (determinismus.md §3). */ declare class TrialBalanceProjection { private readonly baseCurrency; private readonly accounts; private readonly journal; constructor(baseCurrency: Currency, accounts: AccountRepository, journal: JournalRepository); compute(params: Record): { rows: Array>; }; } /** * Open items list: deterministic, asOf-capable (time travel via settledAt). Sorting: * voucherDate, then sequenceNumber (determinismus.md §3). */ declare class OpenItemsProjection { private readonly openItems; private readonly vouchers; private readonly journal; private readonly partners; constructor(openItems: OpenItemRepository, vouchers: VoucherRepository, journal: JournalRepository, partners: PartnerRepository); compute(params: Record): { items: Array>; }; private voucherDate; private sequenceNumber; /** * Both `partnerId` and `due` were known here and neither was published. * * The partner was accepted as a FILTER and dropped from the result, so a list could be narrowed to * one debtor and then could not say which. The due date sits on the voucher — which this method * already loads, for the voucher number — and without it the list cannot be aged at all, which is * what a maturity disclosure is built from. * * Null where the voucher names no date: present and null, so "no date agreed" stays * distinguishable from "this view does not say". * * `partnerName` joined the same way and for the same reason (F-CORE-030). The list could name * the invoice but not the customer, and resolving the id meant a second projection inside one * view — which is exactly the mixing an application's read layer is supposed to avoid. A dunning * notice without a recipient is not a notice. Null where the partner is unknown or was never * recorded, so a deleted-from-view partner does not turn into an empty string that reads like a * name. */ private serializeItem; private partnerName; } /** * Account sheet: all movements of an account in the fiscal year with a running balance. * Opening balance = cumulative prior years for balance-carrying accounts, null for income accounts. * Order: sequenceNumber (determinismus.md §3). * * Each line carries the **identity of its entry** and the accounts on the other side of it * (SPEC-021, reported by an embedding app as its F-31). Without the first, a screen that shows a * sheet and lets the reader open a line had to go looking: `journal` with `fromDate` and `toDate` * set to the same day, then filter the day's entries by `sequenceNumber` — a search where a lookup * belongs, for an entry whose identity the caller had two fields ago. Without the second, a * T-account cannot answer the question it raises on every line: *6000 in debit, against what?* * * `contraAccounts` is a **list** on purpose. For a plain entry it holds one account; as soon as a * tax code is involved it holds two or more, and a field called "the counter account" would have to * pick one and thereby invent a fact. Forming it is also the one part only the library can do: the * sheet is an extract of ONE account and the other lines are not in it, so an embedding combining * figures itself is exactly what a bookkeeping API should make unnecessary. */ declare class AccountSheetProjection { private readonly baseCurrency; private readonly accounts; private readonly journal; constructor(baseCurrency: Currency, accounts: AccountRepository, journal: JournalRepository); compute(params: Record): Record; /** * The accounts on the other side of one entry, by number, deduplicated and sorted. * * "Other side" is decided per line, not per sheet: on a debit line the credit accounts answer the * question, and the other way round. An entry that touches the same account on both sides — a * correction within one account — therefore names it here too, which is the honest answer rather * than an empty list. */ private contraAccounts; } /** * The chart of accounts as a screen can afford to read it (F-CORE-028). * * Every field here was already published somewhere, and nowhere a caller could use. `subtype` * and `status` live in `journalExport.data.accounts` — behind five streams with a SHA-256 each, * an archive format answering a question a list view asks on every page load. `datevExport({ * kind: 'accounts' })` is cheap but DATEV-shaped: number, name, type, and by definition nothing * else, because it is master data for an export format rather than the chart itself. * * So the two facts an application needs most were the two it could not get. `subtype` is what * identifies an account's *role* — which one is the bank, which the cash box, which receivables * and payables — and without it an app preselecting a counter account has to read the pack it was * created from, which is the chart the tenant *started* with and not the chart it has. `status` * is the read side of `lockAccount`: the operation exists, and nothing a screen could afford * reported whether it had been used, so the only honest test was to post into the account and * read the refusal. * * Deliberately small: no balances (that is `trialBalance`), no movements (`accountSheet`), no * hashes. Sorted by account number over Unicode code points, like every other ordering here. */ declare class AccountsProjection { private readonly accounts; constructor(accounts: AccountRepository); compute(_params: Record): { accounts: Array>; }; } /** * Fiscal years and their periods, with the status of each (F-CORE-029). * * `closePeriod`, `reopenPeriod` and `closeFiscalYear` decide what may still be posted, and until * this projection nothing on the read side said what they had decided. `systemDescription` names * the invariant — "a closed period accepts no further postings" — without naming the periods. * * What was left was `auditLog`, which records every close and reopen. That is a **trail, not a * state**: replaying it into "period 3 is open" makes the application rebuild library state from a * log, and it is wrong the moment a period is closed by anything that did not pass through that * application. The same shape as the account status — the write side owning state the read side * does not publish. * * `start` and `end` come out with it, and they are not decoration: without them an application * cannot offer a period *list* at all. Twelve months is a guess that a fiscal year running * February to January does not share, and a form that invents them asks for input the ledger will * refuse. * * Ordered by year, periods by their number — the order they fall due in. */ declare class FiscalYearsProjection { private readonly fiscalYears; constructor(fiscalYears: FiscalYearRepository); compute(params: Record): { fiscalYears: Array>; }; } /** * The journal as a screen reads it (F-CORE-031). * * The plainest view a bookkeeping application has, and until now it had two bad ways to fill it. * `journalExport` is lossless and builds five streams with a SHA-256 each, has no window and no * paging — an archive format answering a list view's question, paid for on every page load. * `datevExport` has the window and the weight but is DATEV-shaped, and therefore **lossy for split * entries**: `6000 75.00 + 1500 14.25 against 1200 89.25` collapses into one row and the input-tax * line disappears. Filling a journal view from it would quietly hide every tax line in the books. * * So: `datevExport`'s cost with `journalExport`'s completeness. Every line of every entry, account * numbers resolved, no hashes, no streams. * * **Paging counts entries, not lines**, which is the whole point — a page boundary that fell inside * a split entry would reproduce exactly the defect this projection exists to avoid. `count` is the * number of entries in the window *before* paging, so a page header can say "51–100 of 3,204" * without a second call that costs an export. * * Ordered by `sequenceNumber`, which is already the journal's total order: paging needs a stable * one, and inventing a tie-break where the ledger already has none would be a second answer to a * question that has one. * * Each entry carries `actor` (F-CORE-037): who recorded it. The entry itself has no author — the * fact lives in the audit trail and nowhere else, so a screen showing the journal could show * everything about a posting except who made it. See `entryAuthors`. */ declare class JournalProjection { private readonly accounts; private readonly journal; private readonly vouchers; /** Where the author of a posting lives — required for the same reason as everywhere else. */ private readonly audit; constructor(accounts: AccountRepository, journal: JournalRepository, vouchers: VoucherRepository, /** Where the author of a posting lives — required for the same reason as everywhere else. */ audit: AuditTrail); compute(params: Record): Record; private serialize; } /** * Change history as a projection (F-CORE-014, F-CORE-036; GoBD Rz. 107 ff.). * * Order = recording order of the audit trail, which is already its total order (the sequence in the * store, insertion order in memory). Paging needs a stable one, and inventing a tie-break where the * trail already has none would be a second answer to a question that has one. * * **Filters, because the auditor's question is about one thing.** Until 0.13.0 the only parameters * were `from`/`to`, so "who touched this account", "what happened to this posting" and "what did * this user do" were not askable: the caller had to fetch the whole trail and filter outside. That * is the wrong place twice over — it moves the fastest-growing table in the system across a * boundary to discard most of it, and it makes progressive and retrograde traceability a property * of the embedding rather than of the books. * * All filters combine with AND, and an absent one filters nothing. `count` is the number of records * matching the filters *before* paging, so a page header can say "51–100 of 3,204" without a second * call — the same contract `journal` publishes, and deliberately the same words. */ declare class AuditLogProjection { private readonly audit; constructor(audit: AuditTrail); compute(params: Record): Record; } /** * VAT return reporting keys via taxTags (SF-09). Accrual: posting/supply date; cash: * follows OP settlements (proportional half-up, final remainder exact). Tax bases * per reporting key rounded down to full euros, tax to the cent (api.md v0.3). */ declare class VatReturnProjection { private readonly baseCurrency; private readonly journal; private readonly openItems; private readonly vouchers; private readonly accounts; private readonly registry; private readonly profile; constructor(baseCurrency: Currency, journal: JournalRepository, openItems: OpenItemRepository, vouchers: VoucherRepository, accounts: AccountRepository, registry: TaxCodeRegistry, profile: TaxProfile); compute(params: Record): Record; /** * Postings that touch a tax account without a tax code (F-TAX-013). * * The return is built from tax-*coded* postings — `line.taxTag` is what carries the reporting key * and the base. That is a defensible design and it is silent: posting expense / input tax / bank * by hand balances, satisfies every invariant, and shows correct figures on the accounts and in * the trial balance. Only `vatReturn` reports zero, because nothing told it which key the amount * belongs to. The books look right everywhere except the one place that decides what is filed, * and an application's seed script fell into it on the first attempt. * * So the warning lives here, at the figures, rather than in a projection of its own that whoever * files the return may not open. It is **not** a refusal: correction postings legitimately touch * these accounts, and a library that blocked them would be wrong more often than the caller. * * Which accounts count is the pack's answer, not this code's: `tax_in` and `tax_out` are subtypes * the chart assigns, so a jurisdiction without input-tax deduction simply has no `tax_in` account * and produces no such warning. * * The window is the posting's tax date in both taxation methods. An untagged line has nothing to * attach it to a settlement, so the cash-basis question "when did the money move" has no answer * for it — which is part of what makes it worth reporting. */ private gapWarnings; private registryDirections; private accountDirection; private entryContributions; private tagBaseMoney; private allocateToSettlements; private proportional; } /** * Income statement as a projection over a mapping (SF-09). Sign: credit − debit * (revenue positive, expense negative); netIncome = sum of the positions. * fromPeriod/throughPeriod restrict the range (monthly income statement as BWA basis). */ declare class IncomeStatementProjection { private readonly baseCurrency; private readonly accounts; private readonly journal; private readonly mappings; constructor(baseCurrency: Currency, accounts: AccountRepository, journal: JournalRepository, mappings: MappingRegistry); compute(params: Record): Record; /** * Which mappings this tenant could use — part of the refusal, because the refusal is the only * place a caller learns it. A `default` tenant has none at all: the neutral pack ships no mapping * module, since a jurisdiction-free chart has no lawful statement layout to ship (IMPL-032). That * is a legitimate answer and used to arrive as "requires the parameter mapping", which reads as * *you forgot something* rather than *this pack cannot do this*. */ private mappingRefusal; } /** * Balance sheet as a projection (SF-10): cumulative as of the reporting date. A position with * includesNetIncome contains the cumulative net income + its own balance. * Side (v0.5/SPEC-007): `side` at the root node; assets = debit−credit, * liabilitiesAndEquity = credit−debit. Default: assets. */ declare class BalanceSheetProjection { private readonly baseCurrency; private readonly accounts; private readonly journal; private readonly mappings; constructor(baseCurrency: Currency, accounts: AccountRepository, journal: JournalRepository, mappings: MappingRegistry); compute(params: Record): Record; /** * Which mappings this tenant could use — part of the refusal, because the refusal is the only * place a caller learns it. A `default` tenant has none at all: the neutral pack ships no mapping * module, since a jurisdiction-free chart has no lawful statement layout to ship (IMPL-032). That * is a legitimate answer and used to arrive as "requires the parameter mapping", which reads as * *you forgot something* rather than *this pack cannot do this*. */ private mappingRefusal; } /** * Cash-basis accounting as a projection over the double-entry journal — rules R1–R7 * (euer-projektions-beweis.md). Cash effect via money accounts, OP link on * settlement (proportional), 10-day rule, VAT cash-effective, R7 includeNonCash. * Bound to the calendar year: a deviating FY → E_CASHBASIS_DEVIATING_FISCAL_YEAR. */ declare class CashBasisProjection { private readonly baseCurrency; private readonly accounts; private readonly journal; private readonly openItems; private readonly vouchers; private readonly fiscalYears; private readonly mappings; constructor(baseCurrency: Currency, accounts: AccountRepository, journal: JournalRepository, openItems: OpenItemRepository, vouchers: VoucherRepository, fiscalYears: FiscalYearRepository, mappings: MappingRegistry); compute(params: Record): Record; private assertCalendarYearFiscalYears; private bankFlow; private assignYear; private inTenDayWindow; private sourceLines; private proportional; /** * R6: category = mapping label, otherwise the account's own name. * * The fallback keeps the money visible, and that is the important half — but on its own it is also * silent, and this report is the one that gets copied onto an official form. An account that falls * back appears under a name that matches no line of that form, and nothing said so. The income * statement has warned about its gaps since it gained the catch-all; this one did not, which is the * wrong way round: the statement WITHOUT diagnostics was the statement that goes to the tax office. * * Only when a mapping was requested at all — without one every account "falls back" by definition, * and warning about all of them would be noise, not information. */ private label; /** Summed from the serialized rows, not from the map: what is reported and what is totalled agree. */ private total; private serializeBucket; } /** * Asset register (a jurisdiction may mandate it even under cash-basis accounting). Sorting: * acquisition date, then ID (deterministic). */ declare class AssetRegisterProjection { private readonly assets; constructor(assets: AssetRepository); compute(params: Record): { assets: Array>; }; } declare class JournalExportProjection { private readonly tenantId; private readonly tenantName; private readonly baseCurrency; private readonly journal; private readonly accounts; private readonly vouchers; private readonly partners; private readonly audit; private readonly clock; constructor(tenantId: Uuid, tenantName: string, baseCurrency: Currency, journal: JournalRepository, accounts: AccountRepository, vouchers: VoucherRepository, partners: PartnerRepository, audit: AuditTrail, clock: Clock); compute(params: Record): Record; private static formatEntry; /** * Field catalog (GoBD Z3 description standard): name, type, meaning — **complete** for every * stream the export carries. * * Complete is the whole point and it was not true until 2026-08-28 (IMPL-038). The catalogue * named 4 of the account's 8 fields, 2 of the voucher's 12, 4 of the audit record's 9, missed * `voucherDate` on the posting, and did not mention the `partners` stream at all — so an auditor * reading the description and the data side by side found fields in the data the * self-description does not admit to. A Z3 data set that under-describes itself is the one * failure this field exists to prevent. * * Keyed by stream and filtered to the streams actually exported, because `partners` is * conditional: describing a stream that is not on the carrier is the same defect mirrored. */ private fieldCatalog; } /** * DATEV export (F-IO-005): posting batch, account labels, partner * master data. Tax lines arise on the DATEV side from the BU key and are * therefore folded into the base line; composite postings into sub-lines. */ declare class DatevExportProjection { private readonly journal; private readonly accounts; private readonly vouchers; private readonly partners; private readonly registry; constructor(journal: JournalRepository, accounts: AccountRepository, vouchers: VoucherRepository, partners: PartnerRepository, registry: TaxCodeRegistry); compute(params: Record): Record; private entryRows; private splitEntry; private accountRows; private partnerRows; } /** * Mapping import (api.md): overlap → E_MAPPING_OVERLAP; gaps are not an * error but gapWarnings[] with the catch-all position `_unassigned`. Checked against * the actually existing accounts per mapping kind. */ declare class MappingImporter { private readonly accounts; private readonly registry; private readonly tenantId; private readonly audit; /** Where the import is kept, so it survives the process that made it (SPEC-015). */ private readonly configStore; constructor(accounts: AccountRepository, registry: MappingRegistry, tenantId?: Uuid | null, audit?: AuditWriter | null, /** Where the import is kept, so it survives the process that made it (SPEC-015). */ configStore?: TenantConfigStore | null); import(input: Record): Record; private relevantAccounts; } declare class TaxService { private readonly baseCurrency; private readonly registry; private readonly profileValue; private readonly journal; private readonly taxRoundingGranularity; private readonly tenantId; private readonly audit; /** Where a profile change is kept, so it outlives this object (SPEC-015). */ private readonly configStore; constructor(baseCurrency: Currency, registry: TaxCodeRegistry, profileValue: TaxProfile, journal: JournalRepository, taxRoundingGranularity?: string, tenantId?: Uuid | null, audit?: AuditWriter | null, /** Where a profile change is kept, so it outlives this object (SPEC-015). */ configStore?: TenantConfigStore | null); profile(): TaxProfile; registryHandle(): TaxCodeRegistry; expand(input: Record): Record; setProfile(input: Record): TaxProfile; private tag; private parseDate; private parseMoney; } /** * Appropriation of profit as a named operation (F-CORE-024/SF-25, expansion). * * **Why this is not something `closeFiscalYear` does.** Appropriating a result is a *resolution* * — § 29 GmbHG, § 174 AktG and their equivalents elsewhere — and which part is distributed, put * into reserves or carried forward is not something a library can derive from the books. It is * also dated when the resolution is passed, which normally falls in the *following* fiscal year; * a close that booked it would have to invent a date it does not have. So summae does not decide, * it expands: the caller states the decision, the pack supplies the accounts. * * Before this operation existed the caller had to know the account numbers — the one part of the * bookkeeping where an embedding still had to (`2300 an 2100`). That is exactly where an agent or * an application guesses, and a guessed account number is a wrong posting rather than an error * message. Here a wrong target is refused by name and a wrong amount by the books. * * **What may be appropriated** is the result *not yet appropriated*, and that figure is not computed * here: `unappropriatedResult` publishes it and this operation asks the same projection, so the * number a user reads and the number this refuses against cannot drift apart. It is also the figure * the balance sheet reports in its `includesNetIncome` position. * * A loss appropriates the other way round (allocation account in credit), and the amounts stay * positive in the input either way: the direction follows from the books, not from a sign the * caller has to get right. * * The SAME shape lives in the PHP ResultAppropriationService. */ declare class ResultAppropriationService { private readonly baseCurrency; private readonly accounts; private readonly journal; private readonly ledger; private readonly audit; private ruleModule; constructor(baseCurrency: Currency, accounts: AccountRepository, journal: JournalRepository, ledger: Ledger, audit: AuditWriter); setRuleModule(ruleModule: Record): void; appropriate(input: Record): Record; /** What the pack offers, or the refusal that says it offers nothing. */ private plug; private parseAppropriations; /** * Amounts arrive in the same shape every posting line uses, and are refused with the same * strictness — including the currency, which v1 pins to the tenant's. */ private parseMoney; /** Which targets this tenant can appropriate to — for a caller that wants to offer a choice. */ offeredTargets(): string[]; } /** * Correcting a deducted input tax when the use of the thing it was deducted for has changed * (F-CORE-056). * * **Where the boundary actually runs, because this one was argued twice.** The *register* — which * assets are under observation, and until when — stays with the embedding application, and for a * reason that survives inspection: the trigger is a **change of use**, which is never posted. A * library that sees only postings cannot see the day a van starts being driven privately. What does * not stay outside is the arithmetic. The argument that put it there was *"a figure produced wrongly * would look exactly as authoritative as one produced rightly"* — which is a reason to compute it * where figures are fixture-pinned, deterministic and verified across two languages, not a reason to * compute it nowhere. * * **It is an expansion, not a projection, and that was the second correction.** A projection reads * the journal; this reads none of it — every input comes from outside. As an expansion it fits * exactly, and it is the better design for an unrelated reason: the correction gets **booked** * rather than computed and handed back for somebody else to book. * * **Socket and plug, with an unusually clean seam.** The mechanism is a pro-rata correction over an * observation period with de-minimis thresholds — the same shape wherever such a rule exists. Every * number is the pack's: how many years the period runs for which kind of thing, the two thresholds, * the accounts, and the key the correction is reported under. The `us` pack simply declares no such * module, and `adjustInputTax` then says so instead of inventing a period. */ declare class InputTaxAdjustmentService { private readonly baseCurrency; private readonly accounts; private readonly vouchers; private readonly ledger; private readonly ids; private readonly audit; private ruleModule; constructor(baseCurrency: Currency, accounts: AccountRepository, vouchers: VoucherRepository, ledger: Ledger, ids: IdGenerator, ruleModule?: Record, audit?: AuditWriter | null); setRuleModule(ruleModule: Record): void; adjust(input: Record): Record; private notDue; /** * `originalInputTax × delta% ÷ years`, with one rounding at the end. `div` runs at big.js's * default twenty decimal places, which is the scale the PHP side divides at for exactly this * reason: rounding twice at the same two scales in both languages is what makes the last cent * equal. */ private yearlyCorrection; private accountsFrom; private correctionYears; private packModule; private post; private requireString; private requireDate; private requireMoney; private optionalMoney; private requirePercent; private optionalPercent; } /** * Tenant: bookkeeping unit, top-most data boundary (glossary `tenant`). Bundles * ports + services of one instance. Grows with the slices (Tax/Partner/Asset/ * Costing follow); the adapter replaces only the ports. */ declare class Tenant { readonly id: Uuid; readonly name: string; readonly baseCurrency: Currency; readonly accounts: AccountRepository; readonly fiscalYears: FiscalYearRepository; readonly vouchers: VoucherRepository; readonly journal: JournalRepository; readonly openItems: OpenItemRepository; readonly assets: AssetRepository; readonly partners: PartnerRepository; /** Reachable from the tenant like every other repository, so `costingRuns` can read it. */ readonly costingRuns: CostingRunRepository; /** Same, for stock (F-CORE-050) — `inventoryValuation` reads it. */ readonly inventoryValuations: InventoryValuationRepository; /** Same, for provisions (F-CORE-051) — `provisionRegister` reads it. */ readonly provisions: ProvisionRepository; /** Same, for prepaid and deferred items (F-CORE-053) — `deferralRegister` reads it. */ readonly deferrals: DeferralRepository; readonly audit: AuditTrail; readonly ledger: Ledger; readonly tax: TaxService; readonly assetService: AssetService; readonly resultAppropriation: ResultAppropriationService; readonly costing: CostingService; readonly inventory: InventoryService; readonly provisionService: ProvisionService; readonly deferralService: DeferralService; /** * The input-tax adjustment (F-CORE-056). No repository: the register of what is under * observation is the embedding application's, because its trigger — a change of use — is never * posted. Only the arithmetic and the posting live here. */ readonly inputTaxAdjustment: InputTaxAdjustmentService; readonly partnerService: PartnerService; readonly mappings: MappingRegistry; readonly clock: Clock; readonly ids: IdGenerator; /** * Which pack this tenant was composed from — null for an inline rule bundle, where there * is no manifest to name. Provenance, not rules: it exists so the system description can * say what the books were kept under (F-IO-007). */ readonly packIdentity: { id: string; version: string; } | null; /** Where configuration changes are kept; null when this tenant has no record (SPEC-015). */ readonly configStore: TenantConfigStore | null; /** * What the embedding declares about the identity behind `actor` (SPEC-020). Null = it has not * said, which `systemDescription` reports as null rather than as "no". Not stored: it describes * the running installation, not the books. */ readonly actorAuthentication: { declared: boolean; method: string | null; } | null; /** * Which legal forms the pack knows and which one this tenant is (F-CORE-039). Unlike * `actorAuthentication` this one IS stored — it describes the entity whose books these are, not * the installation running them, and changing it is an audited event with a date. */ readonly legalForms: LegalFormRegistry; readonly entityProfile: EntityProfileService | null; constructor(id: Uuid, name: string, baseCurrency: Currency, accounts: AccountRepository, fiscalYears: FiscalYearRepository, vouchers: VoucherRepository, journal: JournalRepository, openItems: OpenItemRepository, assets: AssetRepository, partners: PartnerRepository, /** Reachable from the tenant like every other repository, so `costingRuns` can read it. */ costingRuns: CostingRunRepository, /** Same, for stock (F-CORE-050) — `inventoryValuation` reads it. */ inventoryValuations: InventoryValuationRepository, /** Same, for provisions (F-CORE-051) — `provisionRegister` reads it. */ provisions: ProvisionRepository, /** Same, for prepaid and deferred items (F-CORE-053) — `deferralRegister` reads it. */ deferrals: DeferralRepository, audit: AuditTrail, ledger: Ledger, tax: TaxService, assetService: AssetService, resultAppropriation: ResultAppropriationService, costing: CostingService, inventory: InventoryService, provisionService: ProvisionService, deferralService: DeferralService, /** * The input-tax adjustment (F-CORE-056). No repository: the register of what is under * observation is the embedding application's, because its trigger — a change of use — is never * posted. Only the arithmetic and the posting live here. */ inputTaxAdjustment: InputTaxAdjustmentService, partnerService: PartnerService, mappings: MappingRegistry, clock: Clock, ids: IdGenerator, /** * Which pack this tenant was composed from — null for an inline rule bundle, where there * is no manifest to name. Provenance, not rules: it exists so the system description can * say what the books were kept under (F-IO-007). */ packIdentity?: { id: string; version: string; } | null, /** Where configuration changes are kept; null when this tenant has no record (SPEC-015). */ configStore?: TenantConfigStore | null, /** * What the embedding declares about the identity behind `actor` (SPEC-020). Null = it has not * said, which `systemDescription` reports as null rather than as "no". Not stored: it describes * the running installation, not the books. */ actorAuthentication?: { declared: boolean; method: string | null; } | null, /** * Which legal forms the pack knows and which one this tenant is (F-CORE-039). Unlike * `actorAuthentication` this one IS stored — it describes the entity whose books these are, not * the installation running them, and changing it is an audited event with a date. */ legalForms?: LegalFormRegistry, entityProfile?: EntityProfileService | null); static inMemory(name: string, baseCurrency: Currency, clock?: Clock, ids?: IdGenerator, dimensions?: DimensionRegistry, taxCodes?: TaxCodeRegistry, taxProfile?: TaxProfile, mappings?: MappingRegistry, taxRoundingGranularity?: string, packIdentity?: { id: string; version: string; } | null, actorAuthentication?: { declared: boolean; method: string | null; } | null, /** * The constraint socket's second plug (F-CORE-042). Appended rather than slotted next to * `dimensions`, where it belongs by subject: thirteen call sites take the defaults, and moving * a positional parameter to keep two related arguments adjacent would have edited all of them * to say the same thing they already say. */ combinations?: AccountCombinationRegistry): Tenant; /** * Build a tenant from arbitrary ports (service wiring stays here in the core). * `inMemory` uses this with in-memory ports; the persistence adapter * (`@superheld/summae-knex`) passes in DB-backed ports — the same * `Tenant`, only different ports. */ static fromPorts(tenantId: Uuid, name: string, baseCurrency: Currency, ports: { accounts: AccountRepository; fiscalYears: FiscalYearRepository; vouchers: VoucherRepository; journal: JournalRepository; openItems: OpenItemRepository; assets: AssetRepository; partners: PartnerRepository; costingRuns: CostingRunRepository; inventoryValuations: InventoryValuationRepository; provisions: ProvisionRepository; deferrals: DeferralRepository; audit: AuditTrail; }, clock: Clock, ids: IdGenerator, dimensions?: DimensionRegistry, taxCodes?: TaxCodeRegistry, taxProfile?: TaxProfile, mappings?: MappingRegistry, taxRoundingGranularity?: string, packIdentity?: { id: string; version: string; } | null, /** * Where configuration changes are kept (SPEC-015). Null leaves the four configuration * operations effective for this object only — which is what every tenant did before the * record existed, and what an adapter without the table still does. */ configStore?: TenantConfigStore | null, /** See the constructor: the embedding's declaration about `actor` (SPEC-020). */ actorAuthentication?: { declared: boolean; method: string | null; } | null, /** The constraint socket's second plug (F-CORE-042) — see `inMemory` for why it is last. */ combinations?: AccountCombinationRegistry): Tenant; } /** * Generic entry into all operations and projections of a tenant — * the interface for CLI and conformance runner. Names exactly per api.md. * Grows with the slices; the unimplemented reports E_NOT_IMPLEMENTED. */ declare class TenantOperations { private readonly tenant; constructor(tenant: Tenant); execute(op: string, input: Record): Record; project(name: string, params: Record): Record; } /** * Reading projection parameters — one place that says what each declared type means. * * The types come from the parameter contract (`testing/testsuite/schema/api-parameters.json`), * which the dispatcher enforces before a projection ever sees the params. So the readers below * are NOT validation: by the time a projection runs, a declared parameter is either absent or * of the right type. They exist because the previous idiom — `typeof params.x === 'number' ? x * : ` at every single call site — used the type check as a silent policy decision, and * a wrong value became a plausible default 25 times over. * * They still do the check, for the two cases the dispatcher does not cover: a projection * constructed directly (tests, embedding code) and TypeScript's own narrowing, which cannot * know what the dispatcher already proved. */ /** * The types the parameter contract knows. * * The first four describe a projection's parameters, which are scalars by nature. Operations * take structure as well — a voucher, a list of lines, an amount — so `object`, `array` and * `money` came with the operation contract (F-9). Their *inner* shape stays the operation's * business: this contract answers what a key may be, not what a voucher looks like. * * `money` is worth its own type rather than being an `object`, because the mistake it catches is * a real one: an amount passed as a JSON number was silently ignored by every operation that read * it with an is-object check, and the operation carried on with its default. */ type ParameterType = 'integer' | 'string' | 'date' | 'boolean' | 'object' | 'array' | 'money'; /** What the contract says about one parameter. */ interface ParameterSpec { readonly type: ParameterType; /** Declared for completeness; enforcing it is a separate step (see validateProjectionParams). */ readonly required?: true; /** Passed by existing fixtures, read by no implementation — declared so the gap is visible. */ readonly acceptedWithoutEffect?: true; /** * What is inside a structure (SPEC-017). `fields` for an object, `element` for an array — the * declaration every element is checked against. Recursive, and it stops where the declaration * stops: a shape that is not written out is not checked, which is a decision the contract's * author makes visibly rather than one that happens. */ readonly fields?: Readonly>; readonly element?: ParameterSpec; /** Another schema owns this structure. The value is the reason, and it is required. */ readonly opaque?: string; } /** * The parameter contract of the projections, as data. * * This table is a hand-kept copy of `testing/testsuite/schema/api-parameters.json`, the * normative source. The core reads no files by design (framework-free, no I/O), so the table * has to live in the code — and a copy that nothing compares drifts. `projection-parameters.test.ts` * (PHP: `ProjectionParametersTest`) asserts this constant equals that file, in both languages. * Change the schema in the knowledge base, mirror it, and the tests tell you what to change here. */ declare const PROJECTION_PARAMETERS: Readonly>>>; /** * Checks a projection's params against the contract — once, at the dispatcher, instead of * 25 times inside the projections. * * Two rules, both about telling "absent" from "wrong": * - a parameter that is not declared is a caller mistake, not something to ignore. Ignoring * it turned a misspelled `fiscalYear` on vatReturn into a plausible ANNUAL figure where a * quarter was asked for, and `includeZeroBalance` into a flag that did nothing. * - a declared parameter present with the wrong type is rejected, never coerced. `2026.4` is * not a year; whoever meant "year 2026, period 4" has to say which. * Absent keeps the documented default — that is not this function's business, it is the * reader's (`parameters.ts`). * * `null` counts as absent: JSON callers and CLI wrappers write an omitted parameter both ways, * and every projection has treated it as absent since long before this contract existed. */ declare function validateProjectionParams(name: string, params: Record): void; /** * The input contract of the operations, as data — the write-side twin of * `PROJECTION_PARAMETERS` (F-9). * * The asymmetry this closes was the wrong way round for a long time. Every projection parameter * was declared and an undeclared one was `E_INPUT_INVALID` naming the parameter; `execute()` read * what it recognised out of the input and ignored the rest. So a typo in a *read* failed loudly * and a typo in a *write* was silent — and a write is the one that ends up in the books. * * Three defects had already come out of that silence: `expandTax` takes `date` where its * neighbours take `voucherDate`/`entryDate`, so the wrong name yielded a tax-version error naming * a field that was supplied; a wrong `direction` fell through to a default and booked an incoming * invoice inverted (IMPL-013, hardened by hand afterwards); and every numeric input was read with * a `typeof value === 'number'` check, so a form's `"30"` was not rejected but *ignored*, and the * documented default stood in its place. * * Same table, same source, same drift test as the projections: this is a hand-kept copy of * `testing/testsuite/schema/api-parameters.json` (the core reads no files by design) and * `operation-parameters.test.ts` asserts the two are equal, in both languages. * * **What it does not do: enforce `required`.** The flag is declared, and the check stays where it * is. An operation missing its subject already fails, and it fails with a better code than this * layer could give — `E_VOUCHER_UNKNOWN`, `E_ASSET_UNKNOWN`, `E_ENTRY_NO_VOUCHER` say what is * missing; a central `E_INPUT_INVALID` would say less and would overwrite error codes the fixtures * pin. The finding is about inputs that are *present and wrong*, and that is what this catches. */ declare const OPERATION_PARAMETERS: Readonly>>>; /** * Checks an operation's input against the contract — once, at the dispatcher, instead of inside * 31 handlers. * * The rules are the projections' rules, deliberately: an undeclared key is a caller mistake * rather than something to ignore, and a declared key present with the wrong type is rejected * rather than coerced. `null` counts as absent, because JSON callers and CLI wrappers write an * omitted input both ways and every handler has read it that way since long before this existed. * * An operation the table does not know is left alone: an unknown name is the dispatcher's own * error (`E_NOT_IMPLEMENTED`) and must not be reported as an input problem on the way there. * `operation-parameters.test.ts` holds the table's key set against `API_OPERATIONS`, so "not * known here" cannot quietly mean "not checked". */ declare function validateOperationInput(op: string, input: Record): void; /** * Application-layer composition `postVoucher` (api.md, part of the spec): SF-02/03 * in one call — create voucher, expandTax, post, OP creation. */ declare class PostVoucherService { private readonly tenant; constructor(tenant: Tenant); /** Build + store voucher from `input.voucher` — shared by postVoucher and createVoucher. */ private buildAndAddVoucher; /** createVoucher: create a voucher without posting — makes pack-mode fixtures complete (post/acquireAsset). */ createVoucher(input: Record): Record; post(input: Record): Record; } /** * `createTenant` (SF-01): create a tenant from a profile — immediately postable. Profiles * are versioned rule-module data; the tenant pins the version. */ declare class TenantFactory { private readonly ruleModules; private readonly clock; private readonly ids; constructor(ruleModules: Record, clock: Clock, ids: IdGenerator); create(input: Record): { tenant: Tenant; result: Record; }; /** * The pack the resolved bundle came from, when it came from one. An inline bundle has no * manifest, so there is nothing to name and the description says so rather than guessing. */ private packIdentity; private findById; } interface ModuleRef { kind: string; id: string; version?: string; } interface PackModule { formatVersion?: string; id: string; kind: string; version: string; name?: string; contributes?: string[]; dependsOn?: ModuleRef[]; data: Record; } interface PackManifest { formatVersion?: string; id: string; name?: string; version: string; modules: ModuleRef[]; overrides?: { op: string; ref: ModuleRef; with?: ModuleRef; }[]; taxCodes?: string[]; defaults?: Record; packPolicy: Record; } interface ResolvedPack { id: string; version: string; chartOfAccounts: { accounts: Record[]; }; taxCodes: Record[]; mappings: Record[]; assetAccounts: Record | null; depreciation: Record | null; productionCost: Record | null; inventory: Record | null; provisions: Record | null; deferrals: Record | null; inputTaxAdjustment: Record | null; resultAppropriation: Record | null; legalForms: Record | null; dimensionRules: Record[]; accountCombinationRules: Record[]; accountUsageRules: Record[]; packPolicy: Record; profile: Record; contentDigest: string; } /** * Pick a manifest out of a store by id, optionally pinned to a version. * * A published `(id, version)` names one bundle for good, so a library may hold several versions * of the same pack side by side and an old pin keeps resolving what it always resolved. A request * without a version means "current", and current is the **highest** version, compared segment by * segment and numerically where both segments are numbers (`byVersion`) — the * same rule module references already follow. Picking the first match instead would make the * answer depend on directory iteration order, which is not the same on two machines. */ declare function findManifest(manifests: PackManifest[], id: string | null, version?: string | null): PackManifest; /** * Resolves a manifest against a module store. Throws `DomainError` with the * matching `E_PACK_*`/`E_POLICY_*` code; otherwise returns a fully sound * `ResolvedPack`. */ declare function resolvePack(manifest: PackManifest, moduleSource: PackModule[]): ResolvedPack; /** Converts a ResolvedPack into the `ruleModules` bundle that `TenantFactory` consumes. */ declare function ruleModulesFromResolved(pack: ResolvedPack): Record; /** * An unregistered name is refused, not quietly standardised. The repertoire is closed (decided * 2026-08-16): a pack names one of these four and carries no code, so a name that is not here is a * typo or a pack built against a newer core — and both used to book plain VAT without a word. * `reverse_charge` misspelled as `reverse-charge` produced a normal tax line, on the normal * account, in the normal VAT return box. Nothing about the output said the mechanism had been * dropped. * * `E_PACK_INCOHERENT` because that is what it is: the modules resolve, but the bundle asks for a * mechanism that does not exist. The resolver calls this too, so a composed pack fails at * `resolvePack`/`init` rather than at the first posting. */ /** * The registered repertoire, in registration order. Published because a *document* that names the * mechanisms — `docs/gobd-conformance.md` does — is making a checkable claim, and a claim nothing * checks is how that census came to describe a pack that had already moved on. */ declare function allTaxMechanisms(): readonly string[]; export { ACCOUNT_SUBTYPES, API_OPERATIONS, API_PROJECTIONS, AUDITED_EVENTS, Account, AccountNumber, type AccountRepository, AccountSheetProjection, type AccountStatus, type AccountSubtype, type AccountType, AccountsProjection, Asset, AssetRegisterProjection, type AssetRepository, type AssetRoute, AssetService, type AuditChanges, type AuditCriteria, AuditLogProjection, AuditRecord, type AuditTrail, BalanceSheetProjection, CalendarDate, CashBasisProjection, CashJournalProjection, type Clock, CostingRun, type CostingRunRepository, CostingService, Currency, CurrencyMismatch, DEFERRAL_KINDS, DatevExportProjection, Deferral, type DeferralInstalment, type DeferralRelease, type DeferralRepository, DeferralService, DeterministicIdGenerator, DimensionRegistry, type DimensionRuleData, type DimensionTypeData, DimensionValue, type DimensionValueData, DomainError, EcSalesListProjection, EntryLine, type EntryStatus, FORMAT_VERSION, FiscalYear, type FiscalYearRepository, type FiscalYearStatus, FiscalYearsProjection, FixedClock, type IdGenerator, InMemoryAccountRepository, InMemoryAssetRepository, InMemoryAuditTrail, InMemoryCostingRunRepository, InMemoryDeferralRepository, InMemoryFiscalYearRepository, InMemoryInventoryValuationRepository, InMemoryJournalRepository, InMemoryOpenItemRepository, InMemoryPartnerRepository, InMemoryProvisionRepository, InMemoryTenantRecordRepository, InMemoryVoucherRepository, IncomeStatementProjection, InvalidValue, type InventoryCategoryRow, InventoryService, InventoryValuation, type InventoryValuationRepository, JournalEntry, JournalExportProjection, JournalProjection, type JournalRepository, Ledger, Mapping, MappingImporter, type MappingLeaf, MappingRegistry, type ModuleRef, Money, OPERATION_PARAMETERS, OpenItem, type OpenItemKind, type OpenItemRepository, type OpenItemStatus, OpenItemsProjection, type OverheadRate, PROJECTION_PARAMETERS, type PackManifest, type PackModule, type ParameterSpec, Partner, type PartnerRepository, PartnerService, Period, type PeriodDefinition, PeriodRef, type PeriodStatus, PostResult, PostVoucherService, type ProductionCostResult, Provision, type ProvisionMovement, type ProvisionRepository, ProvisionService, type RateWarning, type ResolvedPack, Settlement, type SettlementCause, type SettlementDifferenceKind, type Side, SystemClock, SystemDescriptionProjection, type TaxBaseKind, TaxCode, TaxCodeRegistry, TaxCodeVersion, TaxProfile, TaxService, Tenant, TenantConfigStore, TenantConfigurationProjection, TenantFactory, TenantOperations, type TenantRecordData, type TenantRecordRepository, TrialBalanceProjection, UnfinalizedEntriesProjection, Uuid, UuidV7IdGenerator, VatReturnProjection, Voucher, type VoucherProps, type VoucherRepository, allTaxBaseKinds, allTaxMechanisms, applyAuditCriteria, canonicalJson, emptyTenantConfig, findManifest, isAccountSubtype, isAccountType, isBalanceCarrying, leafMatches, openTenantConfiguration, parseAssetRoute, parseOpenItemKind, parseSettlementCause, parseSettlementDifferenceKind, resolvePack, ruleModulesFromResolved, validateOperationInput, validateProjectionParams };