The platform ships two subledger types —Documentation Index
Fetch the complete documentation index at: https://docs.ntropii.com/llms.txt
Use this file to discover all available pages before exploring further.
expenses and journal_proposals — that any runbook can reuse. Most runbooks need more: a rent-roll line, a capital-call schedule, a fixed-asset depreciation entry. These are runbook-owned types.
A runbook-owned subledger:
- Lives inside the runbook bundle (
runbooks/<name>/subledgers/<type>.py). - Ships its own migration (
runbooks/<name>/migrations/NNN_subledger_<type>.sql). - Registers with
@register_type("<name>", owner_runbook="<runbook-slug>")so the platform registry can disambiguate types across hot-reloads and across runbooks that use the same logical name.
Platform vs runbook-owned
| Aspect | Platform type (expenses, journal_proposals) | Runbook-owned type (rental_statement, custom) |
|---|---|---|
| Code location | ntro.subledger.types.<name> | runbooks/<runbook>/subledgers/<name>.py |
| Migration | ntro-infra/tenant-postgres/migrations/ | runbooks/<runbook>/migrations/ |
| Registration | @register_type("<name>") | @register_type("<name>", owner_runbook="<slug>") |
| Reuse across runbooks | Designed for cross-runbook reuse | Local to the runbook that owns it |
| Schema evolution | Platform release cycle | Runbook release cycle |
owner_runbook= is the boundary marker — it tells the registry “this type belongs to that runbook” so the same logical name can carry different shapes in different bundles without conflicting, and so hot-reloading the runbook re-registers cleanly.
Worked example — RentalStatementRow
From runbooks/nav-monthly-journals/subledgers/rental_statement.py:
- Subclasses
Row— gets the standard column block for free (id,entity_id,period,task_id,status,source_ref,validation_errors,raw_payload, timestamps). - Domain validators live on the row class — same Pydantic
model_validatorpattern asExpenseRow/JournalProposalRow. - No
propose_for_gl— this type stages period-scoped statement facts; it’s read bynav-monthly-journals’s journal proposer and never posts to the GL directly. GL handoff is opt-in per type.
Migration
The matching migration ships in the runbook bundle, not the platform schema. It applies on tenant Postgres alongside the platform migrations and creates theledgers.<type> table:
runbooks/<slug>/migrations/*.sql and runs them against the tenant database in filename order. Keep the standard column names exact — the Row base relies on them for SubledgerHandle.insert / query / status mutations.
Using the handle
Once registered + migrated, the type is reachable via the samesubledger.open(...) flow as platform types:
subledgers/__init__.py must import rental_statement (or be discovered by the runbook loader) for subledger_open(name="rental_statement", …) to work.
GL handoff (opt-in)
Add apropose_for_gl classmethod when your type does post to the external GL — same shape as ExpenseRow.propose_for_gl or JournalProposalRow.propose_for_gl. Most pure staging types (rent rolls, raw statements) skip it — they’re read by a downstream proposer that produces the actual journal.
When to use the status enum override
The default SubledgerStatus lifecycle (NEEDS_ATTENTION → PENDING → APPROVED → POSTED/REJECTED/EXCLUDED) covers most types. Override only when your type has fundamentally different states — e.g. a capital_calls type with AWAITING_BANK / PAID / DEFAULTED. Override via Pydantic field annotation on your Row subclass; the column name stays status, only the enum type changes.
Related
Subledgers overview
Row base, standard column block, SubledgerStatus lifecycle.expenses
Platform reference type — full
propose_for_gl example.journal_proposals
Platform reference type — multi-row aggregation pattern.
UI and Temporal signals
HITL row mutations against custom types via workflow signals.