# Nitnem for engineers
The daily banis are a registry of pointers over the verbatim corpus, with the few non-SGGS texts in a separate, labelled, reviewed layer: how it is built and guarded, variants, numbering, the gate.
- Page:
- Updated: 2026-09-27
- Verified against: `417c92f` on 2026-09-25
- From the Sri Guru Granth Sahib Ji — Knowledge Base wiki. Sri Guru Granth Sahib Ji is quoted verbatim, cited by Ang, and never altered.
---
Nitnem — the daily banis — is where the app most needs the prime directive spelled out in data
structures. The corpus has no notion of a bani, several banis are not in Sri Guru Granth Sahib Ji
at all, and one differs by tradition. [ADR-0006](https://docs.gurbanisoul.com/adr/0006-bani-registry-over-verbatim-corpus/)
answers all three without ever copying or blending text.
## The registry
```mermaid
flowchart TB
accTitle: The bani registry points into the verbatim corpus; non-SGGS text is a separate labelled layer
accDescr: build_banis.py reads the seed and the overrides (membership from the ShabadOS database) and writes the banis table and its ordered bani_lines pointers, checked by guard_banis.py and the golden banis vectors. A pointer names a verbatim line of the corpus by id, cited by Ang; non-SGGS text lives in extra_lines, labelled by source, with no Ang, no English, no search and no bookmark.
seed["bani_seed.json · bani_overrides.json"] --> build["build_banis.py"]
build --> guard["guard_banis.py · golden_banis.ndjson"]
build --> banis["banis: key · variant · titleEn"]
banis --> bl["bani_lines: ordered pointers"]
bl -->|lines.id| lines[("lines: verbatim, cited by Ang")]
bl -.->|no Ang · no English ·
no search · no bookmark| extra[("extra_lines: Sri Dasam Granth ·
Ardaas, labelled by source")]
```
- **A bani is an ordered list of pointers.** For every Sri Guru Granth Sahib Ji line the pointer is
`lines.id`; the text rendered is always this project's own reconciled corpus, cited
"Sri Guru Granth Sahib Ji · Ang N". Membership and group boundaries come from the ShabadOS open
database, resolved to our line ids by text match with explicit, reasoned overrides; inside a
group the order is our printed order.
- **Non-SGGS text lives in `extra_lines`** — Jaap Sahib, Tav-Prasad Savaiye, Benti Chaupai
(Sri Dasam Granth, with its Panna) and Ardaas — converted verbatim, labelled by source, never
indexed by FTS, never given an Ang or a translation, never mixed into `lines`, and not
bookmarkable, shareable or in the Study Trail.
- **Variants** share a key: Rehras Sahib `sgpc` (default) and `taksal`; Asa Di Vaar `kirtan`
(default, the chhants of Angs 448–451 interleaved) and `printed` (the Vaar exactly as printed,
Angs 462–475). A range-defined variant carries **verbatim text anchors** — the opening line's
prefix, the closing line's exact text, the markers before it — re-checked by `build_banis.py`
on every build and by `guard_banis.py` in CI, so a corpus rebuild that shifts ids fails loudly.
- **Proof.** The registry is additive (older databases report `banis_available: false` and the
apps hide the feature); `contract/golden_banis.ndjson` pins every bani's line sequence and a
SHA-256 of its Gurmukhi, replayed by the app's `BaniParityTests`.
## Numbering without guessing
Pauri, ashtapadi and salok numbers come **only** from the markers the scripture prints
(`BaniLine.markers`, bare Gurmukhi digits) or a Dasam line's trailing numeral, through
`BaniOutline` — a pure function with a strict validity gate: if the parsed numbers are not the
sequence they must be, the outline is empty and the app hides Contents rather than show a guessed
number. Numbers appear only outside the verse: a contents sheet, a caption, a margin label.
```swift
import Foundation
// MARK: - Bani outline (pauri / ashtapadi / salok numbering)
//
// A reading outline derived ONLY from the verbatim text a bani already carries: the
// `markers` array (bare Gurmukhi digits the scripture prints, e.g. ੧, or ੮ ੨੪ closing an
// ashtapadi), `isHeader`, and the trailing ॥N॥ of Sri Dasam Granth lines. Nothing here
// produces a Gurmukhi string, and numbers are shown only OUTSIDE the verse (a contents
// sheet, a caption, a margin label). Every strategy passes through a strict validity gate:
// if the parsed numbers are not the sequence they must be, the outline is `[]` — the app
// then hides Contents rather than ever showing a guessed number.
public enum BaniSectionKind: String, Sendable, Equatable {
case salok, pauri, ashtapadi, chhant, verse, part
/// How the contents row and the margin label name it.
public var label: String {
switch self {
case .salok: return "Salok"
case .pauri: return "Pauri"
case .ashtapadi: return "Ashtapadi"
case .chhant: return "Chhant"
case .verse: return "Verse"
case .part: return "Part"
}
```
*`gurbani-soul-ios/ios/Packages/GurbaniSearchKit/Sources/GurbaniSearchKit/BaniOutline.swift`, lines 1–25, read at `a386422`: *
Source: [`BaniOutline.swift`](https://github.com/Algorythmos-AI/gurbani-soul-ios/blob/main/ios/Packages/GurbaniSearchKit/Sources/GurbaniSearchKit/BaniOutline.swift) in gurbani-soul-ios.
## The Nitnem day, bands and progress
- **One clock.** `NitnemClock` is the single source of "now" for the home, the reader, the widgets
and the reminders (it honours `SGGS_CLOCK_NOW` in tests). A Nitnem day rolls at **03:00**
(`dayKey(now − 3h)`): Kirtan Sohila read at 22:00 is still complete at 00:30.
- **Bands only order the home**: Amrit Vela 03:00–09:00, morning banis 09:00–17:00, evening
17:00–21:00, night otherwise. Nothing is hidden by the band.
- **Progress** is `nitnem-progress.json` in the App Group, **schema v1, frozen**: per bani the last
sequence and time, the completed days, and since v1.3.0 a verbatim `anchor` plus `nLines` so a
saved position survives a registry rebuild. A file written by a newer schema is loaded read-only
and never overwritten. The reading journey is computed from the completed days; custom sets go
in a separate file later.
- **Two contexts, one reader.** The same bani reader serves Nitnem (next bani in the band, the
band-complete card) and Explore (the next composition on the rail); scripture, outline, settings
and the saved position are identical, keyed by `key` or `key/variant`.
## The review gate
The "verbatim, proven character for character" claim is scoped to Sri Guru Granth Sahib Ji lines
wherever it is made. The non-SGGS layer is not covered by the reconcile proof, so it ships to the
App Store only after a scholar's review is attested in `ios/Resources/NITNEM-REVIEW.md`
(`REVIEWED: true`) and the in-app "under scholarly review" label is switched off in the same
commit. `check_release_license.sh` treats a missing attestation as a warning for a TestFlight
build and a hard failure for `CHANNEL=appstore`; `make appstore-preflight` refuses to submit a
build that was not archived on that channel. The review has been done: all 1,191 non-SGGS lines
were compared with the SGPC Nitnem Gutka by a Granthi and attested on 2026-09-21, with no
correction required. The full contract — reading steps, frozen XCUITest
identifiers, deep links — is the pinned
[Nitnem spec](https://docs.gurbanisoul.com/ios/nitnem/spec/).