Architecture
Where does Kronika sit, what does it own, and what never crosses its boundaries? Kronika is a client-side gate between three parties: a Git repository (the only admissible evidence), Brama (the only model access), and whatever consumes the documentation afterwards — CI, a scheduler, or the separate site pipeline.
Data flow
repository (working tree + Git history)
| |
collectSources git rev-parse / diff
(selection boundary) (exact SHAs, bounded patch)
| |
v v
one prompt: system rules + file-framed sources [+ diff]
|
v
BramaClient — POST <BRAMA_URL>/v1/chat/completions
bearer + optional HMAC body signature
|
v
write: Markdown --normalize--> preview stdout | atomic --apply
check: JSON verdict --validate--> exit code / findings
^
|
sync loop: manifest + state decide, per document, whether any call
happens at all (baseline / advanced / audit / rewrite) — src/sync/sync.ts
Every model interaction in the kronika CLI is exactly one chat completion
(src/docs/model/brama.ts). There is no streaming, no tool use, no retry loop, no
second provider: if Brama does not answer, the verb fails with the
transport's own sentence (runbook).
What Kronika owns
- The evidence boundary. What a prompt may contain is decided before any
network call: Git-aware discovery, secret-prone exclusions, repository
confinement, byte budgets — all inspectable via
kronika sourceswith no Brama configuration at all (concepts/source-collection). - The verdict contract. Model answers are structured claims that get validated, not trusted: strict JSON verdicts for audits, normalized Markdown for rewrites (fence stripped, empty and NUL content refused).
- The loop's memory.
kronika.sync.json(intent) andkronika.sync-state.json(progress) live in the target repository and are meant to be committed; the audit trail is the Git log itself (concepts/manifest, concepts/state). - Atomicity of the one write it performs.
--applywrites a temporary file beside the target and renames it into place, preserving mode; a failed write leaves the old document intact (src/docs/writer.ts).
What Kronika does not own
- Model routing, authorization, inference. Brama's job. Kronika sends a
modelselector string (defaultany) and resolves credentials by role through Stado, or an explicitly selected owner-only credential file. No secret environment values or fallback endpoints are used (configuration). - Scheduling.
kronika sync --commit --pushis designed to be run by cron, launchd, or CI; Kronika keeps no daemon, no queue, no clock. - Truth of prose. A passing audit means the model found no blocker in the supplied evidence — a maintainer still reviews claims and omissions.
- Publishing. The site pipeline (
kronika-site, underpipeline/) has its own inputs and validators. It resolvesBRAMA_URLor the address Stado publishes in~/.stado/forwards/brama.local; it has no built-in endpoint. Publication belongs to the consuming site's CI (site pipeline). - Execution of repository content. Kronika reads files and runs
git; it never runs the repository's build, tests, or scripts. (The pipeline's drift validator runs only the declared product binary with subcommand words plus--help/--version, allowlisted, never shell-interpreted.)
Trust boundaries
| Boundary | Rule |
|---|---|
| repository → prompt | Untrusted reference data. Both system prompts instruct the model to ignore instructions embedded in sources and diffs (prompt-injection stance), and to never reproduce credentials, tokens, keys, personal data, or private infrastructure addresses. |
| repository → selection | Secret-prone names never selected; symlinks never followed; nothing outside the repository root read or written. |
| credential source → request | The bearer resolved from BRAMA_API_KEY_ROLE rides only in the authorization header. The optional signing identity and resolved secret bind the HMAC signature to the exact body. Secrets do not enter prompts, output, manifest or state. |
| model → repository | Only through validation: verdicts parsed field by field; rewrites normalized and refused when degenerate; only --apply/sync-rewrite writes, atomically, inside the repository. |
| sync → Git history | --commit stages exactly the rewritten documents plus the state file; --push pushes that one commit. Kronika never force-pushes, rebases, or edits history. |
What leaves the machine
One POST per model call to BRAMA_URL, containing: the repository's base
name, the instruction, the selected file manifest and contents, and for
audits the resolved SHAs and the bounded diff. Budgets cap it:
--max-file-bytes, --max-input-bytes, --max-diff-bytes. Nothing else is
transmitted, and kronika sources shows the exact file list beforehand.
Failure posture
Fail closed, loudly, in one line: every error is kronika: <message> on
stderr with exit 1. Oversized diffs are refused rather than truncated;
contradictory verdicts are refused rather than reinterpreted; a failed sync
document keeps its baseline so the next tick retries
the same range. The complete sentence inventory is the
runbook.