Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ Security-relevant events are **persisted locally** (the `AuditEvent` table — t

**Effective configuration (`services/effective_config.py`, `web/admin_config.py`, read-only `/admin/config`).** An admin-only viewer that answers "what config is this process using, where did each value come from, and which optional features are available?" without shell access. `snapshot(session)` introspects the pydantic `Settings` **and** the six admin-editable DB settings rows (Audit/Proxy/MISP/Webhook/AI/OIDC), emitting one row per operationally-meaningful field with a **provenance** — `database` (a settings row is authoritative), `environment` (`settings.model_fields_set`), or `built-in default`. **Secrets never cross the boundary**: a field in the explicit secret set is coerced to a plain `set`/`not set` string server-side (no value or prefix serialised). It also re-runs the prod boot-guards (`config._guard_production`'s checks) **non-fatally** to list every issue, surfaces startup advisories (no-login-path, console-email-in-prod, in-memory-rate-limit-in-prod), and renders feature-capability tiles (environment, AI backend, enabled SSO providers, dev-login, rate limiting, email, RSS poll, MISP/webhook, Typst-available). The page is server-rendered with a CSP-safe Alpine text filter (`configFilter` in `tags.js`, rows carry a `data-search` attribute — no JSON island needed). Read-only: no model, no save path.

**Settings & integrations hub (`web/admin_home.py`, `templates/admin_home.html`, `/admin`).** The map on top of the deep config pages: one tile per admin-configurable subsystem (AI, MISP, proxy, RSS, webhook, SSO under *Outbound integrations*; audit/SIEM and effective config under *Governance*), each with a status pill and a link in, plus the two curation screens (taxonomy, audience groups). Status comes from `effective_config.admin_hub_tiles(session)`, which reads the *same* settings singletons the config pages themselves edit, so the hub can never disagree with the page it links to — it owns no state and has no save path. The pill distinguishes **OFF** (opt-in integration disabled) from **NOT CONFIGURED** (enabled but missing an endpoint or its env-only secret), which is the state an operator most needs flagged: it looks on but cannot deliver. Secrets are only ever consulted as set/not-set, never rendered.

**Admin-configurable (`web/admin_audit.py`, `templates/admin_audit.html`).** Runtime routing config lives on the single `AuditSettings` DB row (enable, chosen methods, min severity, file path, syslog host/port/protocol/facility, http endpoint, verify-TLS), edited live at the admin-only **`/admin/audit`** console (settings form + filterable event trail + "Send test event"). **Secrets stay in env** (`ICEBERG_AUDIT_HTTP_TOKEN`) and are never written to the DB; `services/audit_settings.py` seeds the row from `ICEBERG_AUDIT_*` env defaults on first read. Rail link (`ic-audit`) is admin-only.

### Frontend / design system
Expand All @@ -133,7 +135,11 @@ The portal is a **"command-center" design** — a light, print-like, authoritati

**⌘K command palette.** An additive Alpine overlay in `base.html` (jump-to over the role's nav items, built as a Jinja `jump_items` list); ⌘K/Ctrl-K or the topbar trigger opens it, arrow keys + Enter navigate, Esc closes. It follows the combobox/listbox dialog pattern: focus stays in the palette while open, the active result is exposed with `aria-activedescendant`, and focus returns to the invoking control on close. No backend dependency (live `/api/search` results are a noted future hook).

**Report editor (`report_edit.html`).** A full-height 3-pane workspace (`.editor-shell`): a command header (inline title + lifecycle flow + save-state + Submit/Approve/Publish), then a `1fr 1fr 340px` grid of **markdown ∣ live preview ∣ docked tabbed panel** (Citations · Tags · AI review for editable drafts · Reqs · Audience for admins · Render), with intel-level/TLP/confidence pinned to the dock footer. The dock uses standard tab/tablist/tabpanel semantics with arrow/Home/End roving selection; the taxonomy picker is a combobox/listbox with keyboard add, removal and dismissal. The AI review tab requests advisory judgement, controlled-tag and analytic-challenge suggestions only: analysts edit them locally, explicitly apply them through the ordinary report/tag save flows, and accepted report fields receive AI provenance after a successful save. Disabled/TLP/provider messages remain inline and fail-soft. The markdown pane's **Insert ▾** toolbar drops `[[diamond|figure|ach:ID]]` tokens at the cursor. The single product `<form id="reportform">` is wired across header/pane/footer via the HTML `form=` attribute (no nested forms); it keeps the existing per-section POSTs and live-preview `POST /api/preview/product` binding verbatim, plus **debounced autosave** (1.2s) to the same `/reports/{id}` endpoint behind the header save-state indicator.
**Report editor (`report_edit.html`).** A full-height 3-pane workspace (`.editor-shell`): a command header (inline title + **marking chips** + lifecycle flow + save-state + Submit/Approve/Publish), then a `1fr 1fr 340px` grid of **markdown ∣ live preview ∣ docked tabbed panel** (Citations · Tags · AI review for editable drafts · Reqs · Audience for admins · Render). Intel level, TLP and analytic confidence are the header chips and *only* the header chips — one source of truth, always visible while writing. Each chip lays its real `<select form="reportform">` transparently over itself, so a click opens the native dropdown: no popover code, keyboard- and screen-reader-navigable, and correct before Alpine hydrates (the component only mirrors the chosen option's own text back into the chip, so there is no label map to keep in sync with the enums). The dock uses standard tab/tablist/tabpanel semantics with arrow/Home/End roving selection; the taxonomy picker is a combobox/listbox with keyboard add, removal and dismissal. The AI review tab requests advisory judgement, controlled-tag and analytic-challenge suggestions only: analysts edit them locally, explicitly apply them through the ordinary report/tag save flows, and accepted report fields receive AI provenance after a successful save. Disabled/TLP/provider messages remain inline and fail-soft. The markdown pane's **Insert ▾** toolbar drops `[[diamond|figure|ach:ID]]` tokens at the cursor. The single product `<form id="reportform">` is wired across header/pane/footer via the HTML `form=` attribute (no nested forms); it keeps the existing per-section POSTs and live-preview `POST /api/preview/product` binding verbatim, plus **debounced autosave** (1.2s) to the same `/reports/{id}` endpoint behind the header save-state indicator.

**Dashboard (`dashboard.html`, `web/notebooks.py`).** Every KPI card is a link to the work behind it, and a **"Needs you now"** queue sits above the fold: the products waiting on a reviewer (for anyone who can review) ahead of the viewer's own drafts, capped at five. Both halves are existing lifecycle state (`Report.status` / `author_id`) surfaced as actions — no new model, no new query pattern. Notebook creation is demoted to a collapsed `<details>` so content leads.

**Notebook phases (`notebook_detail.html`, `notebookTabs` in `tags.js`).** The notebook is worked in phases rather than scrolled end to end: the nine sections are grouped into **Collect** (sources, notes, files, figures, indicators) · **Analyze** (Diamond, ACH) · **Produce** (products) · **Trace** (requirements), and one phase's sections render at a time. The old anchor-link collection strip becomes the phase tab bar, carrying the same per-section counts. Nothing is removed or gated: the tabs are real `#section` anchors, the server renders Collect as the default (the other phases are `x-cloak`ed), and a `<noscript>` rule cancels `x-cloak` so a browser without Alpine still gets every section on one page — exactly the previous behaviour.

This is a skin + layout reorganisation over the same routes/forms/Alpine bindings (`tagPicker`/`taxonomyFilter`, the preview/ach/diamond `x-data` blocks, citation autosave) — no behavioural coupling. **Two additive read-only index routes** back the rail: `GET /notebooks` (`notebooks_list.html`, writer-only) and `GET /tags` (`entities_list.html`, named-threat entity browse).

Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ src/iceberg/
web/ # portal routes (Jinja2): common (shared router + guards/helpers) + domain modules
# (notebooks, analytics [diamond/ACH], reports, requirements, feed [stakeholder dissemination],
# feeds [analyst RSS reader], discovery [search/taxonomy/audience], admin_audit, admin_feeds [RSS config],
# admin_proxy [outbound proxy config], admin_oidc [multi-provider SSO config], admin_ai [AI provider config], admin_misp [MISP push config], admin_webhook [publication webhook config], admin_config [read-only effective-config viewer])
# admin_proxy [outbound proxy config], admin_oidc [multi-provider SSO config], admin_ai [AI provider config], admin_misp [MISP push config], admin_webhook [publication webhook config], admin_config [read-only effective-config viewer], admin_home [/admin Settings & integrations hub: status-pill tiles over the config pages])
auth/oidc/ # multi-provider OIDC adapters (base StandardOIDCAdapter + registry + entra/authentik/auth0/okta claim extraction)
services/ # users (OIDC provisioning keyed on (auth_provider,issuer,sub)), oidc_settings (multi-provider config row + enabled_providers resolver, env-only secrets), notebooks, lifecycle, citations/rendering (reports), publication (immutable publish snapshots + atomic publish), requirements, attachments, figures, upload_validation (magic-byte content sniffing for uploads), source_grading (offline Admiralty heuristic), diamond, ach, iocs (light-touch IOC staging CRUD + AI-candidate normalisation: refang + IOCType constraint), product_html (shared report-HTML assembler + inline-embed registry), dissemination (feed delivery + email/webhook job enqueue), jobs (durable OutboxJob outbox: enqueue/lease/retry + worker pass), singleton (conflict-safe settings-row seeding), webhook_settings (publication-webhook config row, env-only token), email, feed (stakeholder feed helpers), feedback (intel-cycle feedback loop / RFI-satisfaction), feeds (inbound RSS ingestion: admin CRUD + bounded SSRF-guarded fetch/parse/sanitise + send-to-notebook), inbound (writer-triggered TAXII/MISP pull → notebook sources/IOCs), misp + misp_settings (outbound MISP push: report indicators → one MISP event, env-only API key), proxy + proxy_settings (global outbound-proxy resolution for httpx), tags, search (FTS dispatch + paginated search_page), attack (ATT&CK Navigator export + coverage matrix), attack_import (Enterprise ATT&CK bundle → TECHNIQUE tags), stix, taxii (read-only TAXII 2.1-shaped serving), related, audience (fail-closed audience-group mutations), ai + ai_settings (governed AI assist + admin-editable provider config row, env-only key, resolver overlays row onto Settings), tradecraft (advisory estimative-language lint), maturity (CTI program maturity & effectiveness dashboard), audit + audit_settings + siem (security audit logging → SIEM), effective_config (read-only resolved-config snapshot for /admin/config: provenance + secret-redaction + prod-guard validation + feature tiles)
services/ # users (OIDC provisioning keyed on (auth_provider,issuer,sub)), oidc_settings (multi-provider config row + enabled_providers resolver, env-only secrets), notebooks, lifecycle, citations/rendering (reports), publication (immutable publish snapshots + atomic publish), requirements, attachments, figures, upload_validation (magic-byte content sniffing for uploads), source_grading (offline Admiralty heuristic), diamond, ach, iocs (light-touch IOC staging CRUD + AI-candidate normalisation: refang + IOCType constraint), product_html (shared report-HTML assembler + inline-embed registry), dissemination (feed delivery + email/webhook job enqueue), jobs (durable OutboxJob outbox: enqueue/lease/retry + worker pass), singleton (conflict-safe settings-row seeding), webhook_settings (publication-webhook config row, env-only token), email, feed (stakeholder feed helpers), feedback (intel-cycle feedback loop / RFI-satisfaction), feeds (inbound RSS ingestion: admin CRUD + bounded SSRF-guarded fetch/parse/sanitise + send-to-notebook), inbound (writer-triggered TAXII/MISP pull → notebook sources/IOCs), misp + misp_settings (outbound MISP push: report indicators → one MISP event, env-only API key), proxy + proxy_settings (global outbound-proxy resolution for httpx), tags, search (FTS dispatch + paginated search_page), attack (ATT&CK Navigator export + coverage matrix), attack_import (Enterprise ATT&CK bundle → TECHNIQUE tags), stix, taxii (read-only TAXII 2.1-shaped serving), related, audience (fail-closed audience-group mutations), ai + ai_settings (governed AI assist + admin-editable provider config row, env-only key, resolver overlays row onto Settings), tradecraft (advisory estimative-language lint), maturity (CTI program maturity & effectiveness dashboard), audit + audit_settings + siem (security audit logging → SIEM), effective_config (read-only resolved-config snapshot for /admin/config: provenance + secret-redaction + prod-guard validation + feature tiles; also `admin_hub_tiles` — the per-subsystem status pills behind /admin)
rendering/ # markdown->HTML, report->PDF (typst), svg (shared diagram helpers: escape/fonts/wrap/placard)
data/ # starter_tags.json (importable starter taxonomy)
templates/ # Jinja2 + Alpine: base (command-center shell: rail+topbar+canvas+⌘K palette), _glyph, _macros,
Expand Down
Loading