docs: add setup-banner.md + rename migration test to test_db_schema_version.py

- Add docs/setup-banner.md: placeholder table, autoescape semantics, security
  note on post-render stripping, diff table vs welcome-template (M-9).
- Update CHANGELOG.md to reference docs/setup-banner.md.
- Rename tests/test_db_migration_v20.py → tests/test_db_schema_version.py
  (file tested SCHEMA_VERSION==22, not just the v20 step; clearer name) (M-10).
This commit is contained in:
ZdenekSrotyr 2026-05-02 20:59:25 +02:00
parent 5bfd8997ea
commit 0ee22f8fb0
3 changed files with 95 additions and 1 deletions

View file

@ -12,7 +12,7 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
### Added
- Admin-editable banner on `/setup` page — admins can author a Jinja2/HTML banner displayed above the auto-generated bootstrap commands. Empty by default (no banner shown). Edit at `/admin/setup-banner`. Endpoints: `GET /api/admin/setup-banner` (returns content + audit), `PUT` to set, `DELETE` to clear, `POST /api/admin/setup-banner/preview` for live preview. Useful for org-specific notes: VPN requirements, support channel, data classification, platform prerequisites.
- Admin-editable banner on `/setup` page — admins can author a Jinja2/HTML banner displayed above the auto-generated bootstrap commands. Empty by default (no banner shown). Edit at `/admin/setup-banner`. Endpoints: `GET /api/admin/setup-banner` (returns content + audit), `PUT` to set, `DELETE` to clear, `POST /api/admin/setup-banner/preview` for live preview. Useful for org-specific notes: VPN requirements, support channel, data classification, platform prerequisites. See `docs/setup-banner.md` for the full placeholder reference and security notes.
- DuckDB schema v22: `setup_banner` singleton table for the per-instance banner. Auto-migration v21→v22 on first start.
- Customizable analyst welcome prompt (`CLAUDE.md` generated by `da analyst setup`). Default ships at `config/claude_md_template.txt` (now Jinja2 syntax). Admins override per instance via the `/admin/welcome` editor or `PUT /api/admin/welcome-template`. New endpoint `GET /api/welcome` returns the rendered prompt for the calling user, with `marketplaces` filtered by RBAC. See `docs/welcome-template.md` for the full placeholder reference.
- `POST /api/admin/welcome-template/preview` — renders arbitrary template content against the calling admin's live context without persisting. Backs the editor's Preview button.

94
docs/setup-banner.md Normal file
View file

@ -0,0 +1,94 @@
# Setup page banner
The setup banner is a block of HTML (or plain text) shown **above** the
auto-generated bootstrap commands on the `/setup` page. Use it for
org-specific operational notes that analysts need before they install the
client: VPN requirements, support channel, data-classification policy,
platform prerequisites, etc.
The banner is empty by default — no content is shown until an admin sets one.
## How to edit
- **Admin UI:** `/admin/setup-banner` — split-pane editor with a placeholder
cheatsheet and a live HTML preview. Click **Save banner** to persist,
**Remove banner** to clear.
- **REST API:**
- `GET /api/admin/setup-banner` — returns `{content, updated_at, updated_by}`.
`content` is `null` when no banner is set.
- `PUT /api/admin/setup-banner` with body `{"content": "..."}` — validates
Jinja2 syntax and stores the banner.
- `DELETE /api/admin/setup-banner` — clears the banner; `/setup` shows no
banner until one is set again.
- `POST /api/admin/setup-banner/preview` with body `{"content": "..."}`
renders arbitrary content against the calling admin's context without
persisting. Backs the editor's live preview.
The banner lives in `system.duckdb` (table `setup_banner`, singleton row id=1).
## Available placeholders
| Placeholder | Type | Notes |
|---|---|---|
| `instance.name` | string | `instance.name` in `instance.yaml` |
| `instance.subtitle` | string | `instance.subtitle` in `instance.yaml` |
| `server.url` | string | full origin of the Agnes server |
| `server.hostname` | string | host part only (no port or path) |
| `user.email` | string | logged-in user, or `null` for anonymous visitors |
| `user.name` | string | logged-in user display name |
| `user.is_admin` | bool | `true` when the visitor is in the Admin group |
| `now` | datetime (UTC, tz-aware) | server time at render |
| `today` | string (`YYYY-MM-DD`) | server date at render |
> **`user` may be `null`** — `/setup` is partly public (anonymous visitors
> get the install one-liner). Always guard user-specific placeholders:
>
> ```jinja2
> {% if user %}Welcome back, {{ user.name }}!{% endif %}
> ```
## Autoescape semantics
The Jinja2 environment runs with `autoescape=True`, which means template
**variable output** (`{{ ... }}`) is HTML-escaped automatically. Literal HTML
in the template source is passed through unchanged — that is how the banner
outputs `<p>` tags, `<strong>`, etc.
To output a literal `<` or `&` from a variable, use the `| safe` filter only
when you are certain the value is trusted:
```jinja2
{# Safe — admin-authored constant: #}
{{ "<strong>VPN required</strong>" | safe }}
{# Dangerous — never pipe user-controlled values through | safe: #}
{{ user.name | safe }} {# do NOT do this #}
```
## Security note
Admin-authored banner content is rendered for **all `/setup` visitors**,
including anonymous users. As a defense-in-depth measure, inline `<script>`
tags, `<iframe>` blocks, `on*=` event handlers, and `javascript:`/`data:`
URI schemes are stripped from the rendered output before it reaches the
browser.
This is **not a full sandbox** — a determined admin can still author arbitrary
HTML with CSS tricks or external resource loads. The stripping is a safety net
against accidental inclusion of dangerous markup (copy-paste from an untrusted
source, etc.), not a substitute for trust in your admin users.
For a stricter posture, add a `Content-Security-Policy` header that disallows
inline scripts and restricts `connect-src`.
## Difference from the welcome template
| | Setup banner | Welcome template |
|---|---|---|
| Location | `/setup` page (partly public) | `CLAUDE.md` in analyst workspace |
| Format | HTML (rendered in browser) | Markdown (consumed by Claude Code) |
| Default | No banner | Ships a default at `config/claude_md_template.txt` |
| Context | `instance`, `server`, `user` (nullable), `now`, `today` | All of the above plus `tables`, `metrics`, `marketplaces`, `sync_interval`, `data_source` |
| RBAC filtering | None — same for all visitors | `marketplaces` filtered per user's group memberships |
See `docs/welcome-template.md` for the welcome-template reference.