# holger-ui — AI context (full API & source reference)
> **Read [ai-context.md](ai-context.md) first.** This is **not** normal prompt context —
> it is a **lookup reference** for exact public-API signatures, selected private
> APIs, complete rustdoc and `file:line` sources. Do not load it for every task.
## Snapshot
| Field | Value |
|---|---|
| Repository | holger-ui |
| Source revision | `11f89df5141d328237d43c8cbed2b8997715b639` |
| Working tree | Clean |
| Reproducible snapshot | Reproducible |
| Generation mode | development |
| Generator version | kvasir 0.2.1 |
| Maintained metadata | `.nornir/ai-context.toml` |
| Generated at | 2026-08-14T10:39:59+02:00 _(source: commit)_ |
## Navigation
Architecture, runtime wiring, security invariants, failure semantics, the trust
lifecycle, the state inventory and the task map all live in [ai-context.md](ai-context.md):
- [Runtime wiring map](ai-context.md#runtime-wiring-map)
- [Non-negotiable invariants](ai-context.md#non-negotiable-invariants)
- [Failure semantics](ai-context.md#failure-semantics)
- [Task-to-file map](ai-context.md#task-to-file-map)
- [Trust and lifecycle](ai-context.md#trust-and-lifecycle)
- [State and persistence](ai-context.md#state-and-persistence)
## Crate and module map
| Category | Count | Included items |
|---|---|---|
| Cargo workspace packages | 1 | holger-ui-web |
| Detached Rust packages | 0 | — |
| Packages with indexed API surface | 1 | 1 workspace + 0 detached |
| Binary targets | 1 | [[bin]] + src/main.rs + src/bin/* |
| Library targets | 0 | packages with src/lib.rs |
The indexed count (1) is the 1 workspace members that expose a `src/` tree plus 0 detached package(s) (own `[workspace]`, indexed for their API but outside the dependency graph). Detached packages **are** in the API index; they are **not** in the workspace dependency graph.
| crate | description | target | modules | items |
|---|---|---|---|---|
| [`holger-ui-web`](#crate-holger-ui-web) | [web-console] The whole SPA: generated gRPC-web clients + routed Leptos views + OIDC PKCE. Single crate; the Vetra theme lives in index.html as plain CSS custom properties. | bin | 19 | 90 |
## Workspace dependency graph
Garmr workspace crates only (external deps excluded); edges deduplicated, kind + feature-gate marked.
_(no entries)_
## Central external dependency hotspots
Only architecturally meaningful dependencies. `Used by` is derived from Cargo metadata.
| Dependency | Used by | Architectural effects | Security/reliability implications | Feature/runtime impact |
|---|---|---|---|---|
| `tonic` | `holger-ui-web` | gRPC transport for the Arrow-Flight ingest service. | The Flight path has no bearer check — collector id is self-declared. | Feature-gated (`flight`). |
| `leptos` | `holger-ui-web` | Web console (WASM CSR SPA), served as static assets. | Separate binary; not linked into the daemon. | Separate binary. |
## High-coupling crates
| Crate | Incoming workspace edges | Outgoing workspace edges | Why changes are high impact |
|---|---|---|---|
| `holger-ui-web` | 0 | 0 | moderately central |
**Dependency validation:**
- duplicate workspace edges after dedup: 0 (must be 0)
- external dependency present in workspace graph: false (must be false)
- workspace crates: 1, workspace edges: 0, external deps: 15
- dependency kind retained per edge; feature-gated edges marked
## API reference
Every exported item (plus security-relevant private items), grouped by change-relevance,
then crate → module. Each entry: signature, `file:line`, and its rustdoc.
### Core contracts
_(no crates in this group)_
### Runtime and CLI
_(no crates in this group)_
### Ingest
_(no crates in this group)_
### Storage, search, query
_(no crates in this group)_
### Detection, policy, catalog, baselines
_(no crates in this group)_
### Agent, LLM, tools
_(no crates in this group)_
### Governance, audit, actions
_(no crates in this group)_
### Retention, backup, HA
_(no crates in this group)_
### API and UI
_(no crates in this group)_
### Development tooling
#### Crate `holger-ui-web`
_[web-console] The whole SPA: generated gRPC-web clients + routed Leptos views + OIDC PKCE. Single crate; the Vetra theme lives in index.html as plain CSS custom properties._
##### Module `holger_ui_web`
**`fn main()`** · `src/main.rs:19`
**`fn main()`** · `src/main.rs:25`
##### Module `holger_ui_web::app`
> The console shell: topbar (brand, nav, session, health) and the routed
> views. Every view is a URL you can share:
>
> ```text
> / overview (dashboard)
> /repositories repository list
> /repo/:name one repository: browse · archive · upload
> /artifact?repo=&ns=&name=&version= artifact detail (query-addressed:
> raw artifact names contain slashes)
> /search?name=&…&repo=&prop=k=v search — axes in the query, auto-run
> /replicate replication (preview-first)
> /login token paste + OIDC/PKCE login
> ```
>
> A directly-opened URL carries no view state, so each view fetches what it
> needs from its address alone. Serving note: any host must fall back to
> index.html for unknown paths (Trunk's dev server does; a reverse proxy
> needs the same rule).
**`fn App() -> impl IntoView`** · `src/app.rs:32`
**`fn HealthBadge() -> impl IntoView`** · `src/app.rs:144`
##### Module `holger_ui_web::components::copy_button`
> Copy-to-clipboard with feedback: the button says what it copies (the
> accessible name carries it too) and confirms with a brief "copied".
**`fn CopyButton(# [prop(into)] label: String, # [prop(into)] value: Signal<String>,) -> impl IntoView`** · `src/components/copy_button.rs:10`
##### Module `holger_ui_web::components::feedback`
> The console's shared feedback vocabulary: status badges, error/empty/loading
> states, and the confirmation dialog for irreversible actions. One rendering
> per state kind — views compose these instead of re-inventing them.
**`enum Tone { Good, Warn, Bad, Neutral }`** · `src/components/feedback.rs:9`
Semantic tone — never color alone: every badge carries its text.
**`fn Badge(tone: Tone, # [prop(into)] label: String) -> impl IntoView`** · `src/components/feedback.rs:28`
**`fn Loading(# [prop(optional, into)] what: String) -> impl IntoView`** · `src/components/feedback.rs:33`
**`fn ErrorText(# [prop(into)] context: String, # [prop(into)] message: String) -> impl IntoView`** · `src/components/feedback.rs:44`
An error is a sentence, not a color: the server's message rides verbatim.
**`fn EmptyState(# [prop(into)] message: String) -> impl IntoView`** · `src/components/feedback.rs:51`
An empty state says what "empty" MEANS here — a blank table never speaks
for itself, and for some backends an empty listing is not "no data".
**`fn FailBox(# [prop(into)] context: String, # [prop(into)] fail: Signal<crate::grpc::Fail>, # [prop(optional)] retry: Option<Callback<()>>,) -> impl IntoView`** · `src/components/feedback.rs:62`
One failed call, rendered from the unified state model: a kind-specific
summary FIRST (auth needed / permission denied / unsupported / backend
unavailable are different situations demanding different next steps),
then the server's verbatim message, the status-code name, the timestamp,
and a retry button when the caller says retrying is safe (reads only —
a mutation is never blind-retried from here).
**`fn ConfirmDialog(open: RwSignal<bool>, # [prop(into)] title: String, # [prop(into)] consequence: String, # [prop(into)] confirm_label: String, busy: RwSignal<bool>, on_confirm: Callback<()>, # [prop(optional, into)] confirm_phrase: Option<Signal<String>>, children: ChildrenFn,) -> impl IntoView`** · `src/components/feedback.rs:119`
Confirmation for irreversible actions. The consequence text is the point:
it must say what cannot be undone, in the domain's own words. The confirm
button disarms while the action runs (double-click safe) — and the caller
keeps its own busy-guard, because THIS dialog is UI, not the safety.
`confirm_phrase` adds a type-to-confirm gate for the most destructive
operations: the confirm button stays disabled until the operator types the
exact target name. It defends against the wrong-target mistake (right
intent, wrong repository) that a plain OK cannot catch. Reactive, because
the target can change while the dialog is closed; the typed text is
cleared on every open so a previous answer can never carry over.
A11y: focus moves into the dialog on open (the phrase input when there is
one — it is inert until the name matches — otherwise Cancel, the safe
default), Tab cycles between the dialog's focusable elements, Escape
cancels (unless busy), and focus returns to the opener on close.
##### Module `holger_ui_web::components::feedback::Tone`
**`fn class(self) -> &'static str`** · `src/components/feedback.rs:17`
##### Module `holger_ui_web::components::skeleton`
> Layout-preserving loading placeholders.
>
> A skeleton is only honest when it has the SHAPE of what arrives: same
> table, same columns, so the page does not jump when data lands. Text-only
> `Loading` stays for small inline waits — skeletons are for the
> table-shaped ones, where a bare "loading…" collapses the layout twice.
>
> Screen readers get the sentence, not the shimmer: the boxes are
> `aria-hidden` and a single visually-hidden live region speaks instead.
**`fn TableSkeleton(# [doc = " How many placeholder rows to draw — pick the page's usual density, not"] # [doc = " a number that implies a count the server has not reported."] # [prop(default = 5)] rows: usize, # [prop(default = 4)] cols: usize, # [prop(into)] what: String,) -> impl IntoView`** · `src/components/skeleton.rs:14`
##### Module `holger_ui_web::grpc`
> gRPC-web wiring — the one place that knows how the browser reaches holger,
> and the one place a credential is attached.
>
> Generated stubs come from `proto/holger.proto` (vendored from the holger
> main repo — that copy is the source of truth; refresh with
> `cp ../holger/proto/holger.proto proto/`). Transport is
> `tonic-web-wasm-client` over fetch; holger's server terminates gRPC-web
> with its tonic-web layer.
**`fn base_url() -> String`** · `src/grpc.rs:31`
Base URL of the holger gRPC-web door. Same-origin under `/grpc` by default:
the dev proxy (Trunk) and any reverse proxy in front of holger mount the
door there, so no CORS ceremony is needed. Override at build time with
`HOLGER_GRPC_WEB_URL` (e.g. a direct server origin — that path needs CORS
opened on the holger side instead).
**`fn storage() -> Option<web_sys::Storage>`** · `src/grpc.rs:51`
**`fn token() -> Option<String>`** · `src/grpc.rs:55`
**`fn set_token(token: &str)`** · `src/grpc.rs:61`
**`fn clear_token()`** · `src/grpc.rs:67`
**`struct AuthInterceptor`** · `src/grpc.rs:77`
Attaches `authorization: Bearer <token>` to every outgoing call when a
token is set; without one, requests go out anonymous (reads may still be
open server-side — that posture is the server's call, not the console's).
**`type Authed`** · `src/grpc.rs:91`
**`fn channel() -> Authed`** · `src/grpc.rs:93`
**`fn admin() -> AdminServiceClient<Authed>`** · `src/grpc.rs:97`
**`fn repositories() -> RepositoryServiceClient<Authed>`** · `src/grpc.rs:101`
**`fn search() -> SearchServiceClient<Authed>`** · `src/grpc.rs:105`
**`fn sboms() -> SbomServiceClient<Authed>`** · `src/grpc.rs:109`
**`fn properties() -> PropertyServiceClient<Authed>`** · `src/grpc.rs:113`
**`fn promotion() -> PromotionServiceClient<Authed>`** · `src/grpc.rs:117`
**`fn replication() -> ReplicationServiceClient<Authed>`** · `src/grpc.rs:121`
**`fn archives() -> ArchiveServiceClient<Authed>`** · `src/grpc.rs:125`
**`async fn who_am_i() -> Result<pb::WhoAmIResponse, tonic::Status>`** · `src/grpc.rs:132`
The server's answer to "who am I here?" — subject, method, effective
global role. Discoverability only: every operation is still checked by
its own gate server-side.
**`fn status_text(s: &tonic::Status) -> String`** · `src/grpc.rs:142`
The server's refusal, verbatim, with the status-code name as fallback when
a transport-level failure carries no message — one rendering for every
gRPC error in the console.
**`struct Fail { code: tonic::Code, message: String, at: String }`** · `src/grpc.rs:156`
One failed call, as the console's unified state model sees it: the code
(classified for rendering), the server's verbatim message, and WHEN.
Never carries the credential — the interceptor owns that and it does not
appear in statuses.
**`fn fail(s: &tonic::Status) -> Fail`** · `src/grpc.rs:162`
##### Module `holger_ui_web::grpc::AuthInterceptor`
**`fn call(&mut self, mut req: tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status>`** · `src/grpc.rs:80`
##### Module `holger_ui_web::oidc`
> OIDC authorization-code login with PKCE (RFC 7636, S256) — the browser-SPA
> shape: a PUBLIC client, no secret, the code exchange bound to a one-shot
> verifier so an intercepted redirect code is useless.
>
> Flow: `begin_login` discovers the issuer, stores verifier + state, and
> leaves the page for the IdP's authorization endpoint. The IdP redirects
> back to the console's own origin with `?code&state`; `complete_login` (run
> once at boot) validates the state echo, exchanges the code (with the
> verifier) at the token endpoint over fetch, hands the access token to the
> same store the pasted-token login uses, and scrubs the query string from
> the address bar. From there the tonic interceptor carries it — the two
> login forms are indistinguishable to the rest of the console.
>
> The IdP must list the console origin as an allowed redirect URI and allow
> it for CORS on the token endpoint — both standard SPA registration steps.
**`struct Discovery`** · `src/oidc.rs:29`
**`struct TokenResponse`** · `src/oidc.rs:35`
**`fn session() -> Option<web_sys::Storage>`** · `src/oidc.rs:39`
**`fn local() -> Option<web_sys::Storage>`** · `src/oidc.rs:43`
**`fn settings() -> (String, String)`** · `src/oidc.rs:49`
The issuer/client-id the operator saved (falling back to build-time
defaults) — they are configuration, not secrets, so localStorage is right.
**`fn save_settings(issuer: &str, client_id: &str)`** · `src/oidc.rs:63`
**`fn b64url(bytes: &[u8]) -> String`** · `src/oidc.rs:70`
**`fn random_urlsafe(n_bytes: usize) -> Result<String, String>`** · `src/oidc.rs:93`
**`async fn sha256_b64url(input: &str) -> Result<String, String>`** · `src/oidc.rs:105`
**`fn redirect_uri() -> Result<String, String>`** · `src/oidc.rs:123`
**`async fn discover(issuer: &str) -> Result<Discovery, String>`** · `src/oidc.rs:130`
**`async fn begin_login(issuer: &str, client_id: &str) -> Result<(), String>`** · `src/oidc.rs:145`
Leave the page for the IdP. Only returns on failure-before-redirect.
**`async fn complete_login() -> Option<Result<(), String>>`** · `src/oidc.rs:183`
Run once at boot. `None` = this page load is not an OIDC callback.
`Some(Ok(()))` = token exchanged and stored; `Some(Err)` = the callback was
recognized but failed (state mismatch, exchange refused, …).
**`async fn exchange(code: String, echoed_state: Option<String>, verifier: String, expected_state: String, window: web_sys::Window,) -> Result<(), String>`** · `src/oidc.rs:219`
##### Module `holger_ui_web::pages::artifact_detail`
> Query-addressed artifact detail. A shared URL carries only the coordinate,
> so every fact is looked up from the address alone. Sections: overview
> facts, actions, SBOM, properties, promotion — with promotion behind an
> explicit consequence-stating confirmation.
**`enum Facts { Found, Absent, Undetermined }`** · `src/pages/artifact_detail.rs:22`
What the exact-match page established about a coordinate's facts. `Absent`
is the ONLY state that may render as "—": it is the only one where the
server actually showed a complete page without the coordinate in it.
**`fn ArtifactDetail() -> impl IntoView`** · `src/pages/artifact_detail.rs:29`
**`fn set_property(props: RwSignal<Option<Result<Vec<grpc::pb::PropertyEntry>, String>>>, write: RwSignal<Option<Result<String, String>>>, busy: RwSignal<bool>, repo: String, id: grpc::pb::ArtifactId, key: String, values: Vec<String>,)`** · `src/pages/artifact_detail.rs:300`
Fire one SetProperty and land the server's returned full map in the shared
signal — the response IS the new truth, no refetch.
The WRITE's outcome lives in its own signal: a refused write says nothing
about what the last successful READ returned, so it must not overwrite the
property list with an error (which would also mislabel the failure as
"failed to fetch properties"). `busy` guards against double submits.
**`fn SbomAttach(repo: Memo<String>, id: Memo<grpc::pb::ArtifactId>, on_attached: Callback<()>,) -> impl IntoView`** · `src/pages/artifact_detail.rs:345`
Attach a CycloneDX document to the coordinate. The server owns validation
and the write gate — its refusal is the UI text, verbatim.
**`fn PropertyEditor(repo: Memo<String>, id: Memo<grpc::pb::ArtifactId>, props: RwSignal<Option<Result<Vec<grpc::pb::PropertyEntry>, String>>>, write: RwSignal<Option<Result<String, String>>>, busy: RwSignal<bool>,) -> impl IntoView`** · `src/pages/artifact_detail.rs:427`
Set a key = comma-separated values on the coordinate. A re-set of an
existing key overwrites its values; removal lives on each table row.
**`fn Promote(repo: Memo<String>, id: Memo<grpc::pb::ArtifactId>) -> impl IntoView`** · `src/pages/artifact_detail.rs:493`
Promote this coordinate into an immutable destination — behind an explicit
confirmation that states the seal-once consequence. The server's ADMIN gate
and the create-once seal are the safety; the dialog makes the
irreversibility readable, and never softens it.
##### Module `holger_ui_web::pages::dashboard`
> Översikt — the operational summary. Everything on this page is a live RPC
> (`Health`, `ServerProfile`, `ListRepositories`); nothing is decorative.
**`fn Dashboard() -> impl IntoView`** · `src/pages/dashboard.rs:11`
##### Module `holger_ui_web::pages::login`
> Both login forms, one credential store. The pasted token and the OIDC
> access token land in the same sessionStorage slot and ride the same
> interceptor — the rest of the console cannot tell them apart.
**`fn Login(logged_in: RwSignal<bool>) -> impl IntoView`** · `src/pages/login.rs:17`
Paste-a-token login. The token is whatever the server's configured auth
methods accept — a static API token or an OIDC/OAuth2 access token issued
by the operator's IdP; the console never sees a password and never mints
anything itself. Stored in sessionStorage (dies with the tab, removed by
Log out) and attached by the interceptor as `authorization: Bearer`.
**`fn SsoLogin() -> impl IntoView`** · `src/pages/login.rs:92`
OIDC authorization-code + PKCE. The console is a PUBLIC client: no secret,
the code exchange is bound to a per-attempt verifier, and the resulting
access token lands in the same store as a pasted one. Issuer + client id
are operator configuration (persisted in localStorage, prefillable at
build time via HOLGER_OIDC_ISSUER / HOLGER_OIDC_CLIENT_ID).
##### Module `holger_ui_web::pages::replication`
> One-shot repository replication with the server's own two-step shape:
> `execute: false` is a PREVIEW and the default; the Execute button unlocks
> only after a preview of the same pair has been seen. An Ok RESPONSE is not
> success — `failed`/`truncated`/`complete` render as their own facts.
**`fn Replicate() -> impl IntoView`** · `src/pages/replication.rs:14`
##### Module `holger_ui_web::pages::repositories`
> Repository list — scannable, with the writability/archive facts as badges
> (never color alone), every name a real link, and the filter/sort state in
> the URL so a filtered view is a shareable address.
**`fn Repositories() -> impl IntoView`** · `src/pages/repositories.rs:14`
##### Module `holger_ui_web::pages::repository_detail`
> One repository's home: metadata header, browse (filter + offset-cursor
> pagination), the archive view when the backend carries a znippy archive,
> and upload when the repository accepts writes.
**`const PAGE_SIZE: i32`** · `src/pages/repository_detail.rs:17`
One page of artifacts per fetch; the view accumulates pages until the
server stops handing back a `next_page_token`.
**`fn RepositoryDetail() -> impl IntoView`** · `src/pages/repository_detail.rs:20`
**`fn Archive(repo: Memo<String>) -> impl IntoView`** · `src/pages/repository_detail.rs:263`
The znippy-archive view: totals from `ArchiveInfo`, paths (with prefix
filter) from `ListArchiveFiles`. Read-gated like every other read.
**`fn Upload(repo: Signal<String>, on_uploaded: Callback<()>) -> impl IntoView`** · `src/pages/repository_detail.rs:340`
The write surface: one file becomes one `PutArtifact`. The name prefills
from the picked file (for a raw repo the name IS the path — type a full
`dir/sub/file.ext` to place it); the server's write gate decides whether
the caller may, and its refusal is shown verbatim.
##### Module `holger_ui_web::pages::search`
> Search — every axis the server's proto actually offers: name, namespace,
> version, path, checksum, a repository restriction, and custom-property
> filters (`PropertyMatch`). The URL is the search state: filters prefill
> from it, a shared address auto-runs, and running a search IS navigating.
**`fn Search() -> impl IntoView`** · `src/pages/search.rs:15`
##### Module `holger_ui_web::util`
> Pure helpers — no web APIs, so they compile and unit-test on the native
> target too (the rest of the crate is wasm-only; hence the allow — native
> builds see no callers).
**`fn display_name(namespace: &str, name: &str) -> String`** · `src/util.rs:7`
`namespace/name` when a namespace exists, else just the name.
**`fn enc(s: &str) -> String`** · `src/util.rs:18`
Percent-encode one URL query component (RFC 3986 unreserved kept) — raw
artifact names carry `/`, so every coordinate that rides a query string
goes through here. Pure Rust: testable natively, no js interop.
**`fn artifact_href(repo: &str, namespace: &str, name: &str, version: &str) -> String`** · `src/util.rs:33`
The shareable address of an artifact's detail view (query-addressed —
names contain slashes).
**`fn fmt_bytes(n: i64) -> String`** · `src/util.rs:44`
`1234567` → `1.2 MB` — sizes are for scanning, not accounting.
**`fn parse_prop_param(raw: &str) -> (String, String)`** · `src/util.rs:63`
One property filter as it rides the search URL: `key` alone (presence) or
`key=value` (exact value among the key's values) — the FIRST `=` splits,
values may contain more.
**`fn prop_param(key: &str, value: &str) -> String`** · `src/util.rs:70`
**`fn confirm_phrase_matches(typed: &str, required: &str) -> bool`** · `src/util.rs:84`
Does the typed confirmation match the phrase the dialog demands?
Exact match after trimming the ends — a paste that picked up a trailing
newline should not block a legitimate confirmation, but a DIFFERENT name
must never pass, and an empty requirement can never be satisfied (a
type-to-confirm gate with nothing to type would be no gate at all).
##### Module `holger_ui_web::util::tests`
**`fn display_name_joins_namespace_only_when_present()`** · `src/util.rs:94`
**`fn enc_keeps_unreserved_and_escapes_the_rest()`** · `src/util.rs:100`
**`fn artifact_href_is_query_addressed_and_escaped()`** · `src/util.rs:106`
**`fn fmt_bytes_scans_well()`** · `src/util.rs:114`
**`fn prop_param_round_trips_first_equals_split()`** · `src/util.rs:123`
**`fn confirm_phrase_demands_the_exact_name()`** · `src/util.rs:132`
**`fn an_empty_requirement_is_never_satisfied()`** · `src/util.rs:145`
##### Module `holger_ui_web::webio`
> Browser-side I/O helpers (wasm-only): file reads and named downloads.
**`fn save_bytes(file_name: &str, data: &[u8], content_type: &str) -> Result<(), String>`** · `src/webio.rs:8`
Hand the bytes to the browser as a named download via a temporary object
URL. The URL is revoked immediately after the click — the blob lives on
until the download completes.
**`async fn read_file(file: &web_sys::File) -> Result<Vec<u8>, String>`** · `src/webio.rs:35`
Read the picked file's bytes off the main thread via `File.arrayBuffer()`.
**`fn set_title(title: &str)`** · `src/webio.rs:43`
Set the browser-tab title — every routed view names itself.
**`fn now_hms() -> String`** · `src/webio.rs:50`
Local wall-clock HH:MM:SS — mutation results carry WHEN they happened.
**`async fn copy_text(text: &str) -> Result<(), String>`** · `src/webio.rs:62`
Put text on the clipboard (async Clipboard API; falls back to a rejected
future outside secure contexts — the caller reports, never panics).
### Benchmarks and test infrastructure
_(no crates in this group)_
## Incomplete-work markers (full)
_None found._
## Tests inventory
**7 unit** test fns across 1 files; **3 integration** test fns across 1 files.
| Crate | Unit test fns | Integration test fns |
|---|---|---|
| `src` | 7 | 0 |
| `tests` | 0 | 3 |
## Size and generation metadata
Measured for the full pack (excludes this footer):
| Metric | Value |
|---|---|
| Bytes | 25349 |
| Lines | 586 |
| Words | 3401 |
| Approx tokens (chars/4) | 6338 |
| Token budget | 7% of 80000 |
| Duplicate-sentence ratio | 0.000 (max 0.000) |
| Duplicate-row ratio | 0.000 (max 0.000) |