diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ac8f89..3e30e28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,48 @@ All notable changes to Armature are documented here. +## v0.8.4 — Documentation Refresh + Surface Sizing Fix + +Eight versions of module additions (v0.7.5–v0.8.3) shipped without a +docs pass. This release brings the public-facing guides up to date and +fixes a CSS layout bug affecting all surfaces. + +**Documentation refresh** + +- `STARLARK-REFERENCE.md` — added `workspace` module section (5 builtins), + `permissions` module section, and `settings.has_capability()` documentation. +- `EXTENSION-GUIDE.md` — added `capabilities` manifest block, `vector(N)` + column type, `user_permissions` and `gate_permission` manifest fields, + updated sandbox permissions list with v0.8.0+ additions (`files.read`, + `files.write`, `workspace.manage`). +- `TUTORIAL-FIRST-EXTENSION.md` — reviewed for v0.7+ accuracy (no changes needed). + +**Surface sizing fix** + +All surfaces using the shell topbar had scroll content clipped at the +bottom by ~44px (the topbar height). Root cause: surface containers were +siblings of `#shell-topbar` inside `.surface-inner`, which used +`height: 100%` without flex layout — the surface div claimed the full +parent height, ignoring the topbar sibling. + +- Fix: `.surface-inner` now uses `display: flex; flex-direction: column` + so the topbar and surface share vertical space via flex layout. +- All surface containers (`.surface-docs`, `.surface-admin`, + `.surface-settings`, `.surface-editor`, `.extension-surface`) changed + from `height: 100%` to `flex: 1; min-height: 0`. +- Inline styles on `surface-team-admin` and `welcome-mount` templates + updated to match. + +**Modified files:** + +- `server/pages/templates/base.html` — `.surface-inner` gains flex column layout +- `src/css/surfaces.css` — `.surface-docs`, `.surface-admin`, `.surface-settings`, `.surface-editor` +- `src/css/extension-surface.css` — `.extension-surface` +- `server/pages/templates/surfaces/team-admin.html` — inline style fix +- `server/pages/templates/surfaces/welcome.html` — inline style fix +- `docs/STARLARK-REFERENCE.md` — workspace, permissions, has_capability +- `docs/EXTENSION-GUIDE.md` — capabilities, vector, user_permissions, gate_permission + ## v0.8.3 — Vector Column Type Extensions can now declare vector columns and perform similarity search. diff --git a/ROADMAP.md b/ROADMAP.md index 863f1bd..545e2a0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,6 +1,6 @@ # Armature — Roadmap -## Current: v0.8.4 — Documentation Refresh + Surface Sizing Fix +## Current: v0.8.5 — Extension Composability Self-hosted extensible platform kernel. Auth, identity, packages, Starlark sandbox, storage, realtime, and ops are kernel primitives. Everything else @@ -434,31 +434,15 @@ HNSW index on pgvector. `starlarkToGoValue` extended with list→JSON. New: `docs/DESIGN-vector-column.md`. Modified: `ext_db_schema.go`, `db_module.go`, `runner.go`, `extensions.go`, `main.go`. 13 new tests. -**v0.8.4 — Documentation Refresh + Surface Sizing Fix** +**v0.8.4 — Documentation Refresh + Surface Sizing Fix** ✅ -Two unrelated items bundled for a clean changeset before composability. - -*Documentation refresh:* Starlark/extension docs have drifted since v0.7.5. -Modules added or significantly changed but not fully documented: -- `workspace` module (v0.8.1) — undocumented in STARLARK-REFERENCE.md -- `permissions` module (v0.7.7) — undocumented in STARLARK-REFERENCE.md -- `settings.has_capability()` (v0.8.2) — not in settings section -- `db.query_similar()` vector section (v0.8.3) — added but needs review pass -- EXTENSION-GUIDE.md — needs `capabilities` manifest block, `vector(N)` column - type, `user_permissions`, `gate_permission` -- TUTORIAL-FIRST-EXTENSION.md — review for v0.7+ accuracy - -*Surface sizing bug:* Extension surfaces (`height: 100%` + `overflow: hidden`) -don't account for the 44px shell topbar. Content is cut off at the bottom -with no scroll. Affected: chat, notes, dashboard, schedules, and likely all -extension surfaces. Root cause: `.extension-mount` is `flex: 1` within the -viewport column, but extensions set `height: 100%` on their root element -which inherits the mount's full height — the topbar's 44px is already -subtracted by flex, but the extension's internal layout overflows because -child containers also use `height: 100%` + `overflow: hidden`, clipping -the last 44px of content. Fix: audit all extension root CSS and ensure -inner scroll containers use `flex: 1; min-height: 0; overflow: auto` -instead of `height: 100%; overflow: hidden`. +Docs pass covering v0.7.5–v0.8.3 additions. STARLARK-REFERENCE.md gains +`workspace` module, `permissions` module, `settings.has_capability()`. +EXTENSION-GUIDE.md gains `capabilities` manifest, `vector(N)` column type, +`user_permissions`, `gate_permission`, updated permissions list. Surface +sizing fix: `.surface-inner` converted to flex column layout so the shell +topbar and surface container share vertical space correctly — eliminates +44px scroll cutoff on all surfaces. **v0.8.5 — Extension Composability** diff --git a/VERSION b/VERSION index ee94dd8..b60d719 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.8.3 +0.8.4 diff --git a/docs/EXTENSION-GUIDE.md b/docs/EXTENSION-GUIDE.md index de28bea..263b22d 100644 --- a/docs/EXTENSION-GUIDE.md +++ b/docs/EXTENSION-GUIDE.md @@ -72,7 +72,7 @@ Every package has a `manifest.json` at its root. Example for a surface: | `icon` | no | Emoji icon for sidebar/menu | | `route` | surfaces | URL path (e.g., `/s/my-surface`) | | `auth` | no | `authenticated` (default) or `public` | -| `permissions` | no | Capabilities requested: `db.write`, `http`, `notifications`, `secrets`, `realtime.publish` | +| `permissions` | no | Sandbox capabilities: `db.write`, `db.read`, `api.http`, `notifications`, `secrets`, `realtime.publish`, `connections.read`, `workflow.access`, `batch.exec`, `files.read`, `files.write`, `workspace.manage` | | `api_routes` | no | Array of `{method, path}` for extension HTTP endpoints | | `api_schema` | no | OpenAPI documentation for extension API routes (see below) | | `db_tables` | no | Table definitions (see below) | @@ -80,11 +80,23 @@ Every package has a `manifest.json` at its root. Example for a surface: | `exports` | libraries | Functions exported for other packages | | `hooks` | no | Event bus subscriptions | | `config_section` | no | Settings/Admin panel injection (see below) | +| `capabilities` | no | Environment requirements (see below) | +| `user_permissions` | no | Permissions this extension registers for users (see below) | +| `gate_permission` | no | Permission checked before `on_request` executes | | `schema_version` | no | Integer for additive schema migrations | ## db_tables Schema -Tables are automatically namespaced as `ext_{package_id}_{table_name}`. Column types: `text`, `int`. Every table gets an auto-generated `id` primary key and `created_at` timestamp. +Tables are automatically namespaced as `ext_{package_id}_{table_name}`. +Every table gets an auto-generated `id` primary key and `created_at` timestamp. + +Column types: `text`, `int`, `vector(N)`. + +The `vector(N)` type stores N-dimensional float vectors for similarity +search via `db.query_similar()`. Storage adapts to the backend: +Postgres + pgvector uses native `vector(N)` with HNSW indexes, +Postgres without pgvector uses `JSONB`, SQLite uses `TEXT`. N can be +1–4096. See the [Starlark Reference](STARLARK-REFERENCE) for query API. ```json "db_tables": { @@ -137,6 +149,51 @@ Extensions can optionally declare an `api_schema` array in their manifest to pro Only `path` and `method` are required. All other fields are optional. Malformed entries are logged and skipped without blocking extension loading. +## Capabilities + +Extensions can declare environment requirements via the `capabilities` +manifest field. The kernel validates these at install time. + +```json +{ + "capabilities": { + "required": ["postgres"], + "optional": ["pgvector", "workspace"] + } +} +``` + +- **required** — install is rejected (HTTP 422) if any capability is missing. +- **optional** — install succeeds with a logged warning. Query at runtime + with `settings.has_capability("pgvector")` to adapt behavior. + +Detected capabilities: `pgvector`, `workspace`, `object_storage`, `s3`, +`postgres`. The admin can view detected capabilities at +**Admin > System > Capabilities** or via `GET /admin/capabilities`. + +## User Permissions + +Extensions can register custom permissions that the admin assigns to +user groups. This controls access to extension features beyond the +sandbox permission model. + +```json +{ + "user_permissions": ["image-gen.use", "image-gen.admin"], + "gate_permission": "image-gen.use" +} +``` + +- **user_permissions** — on install, these are merged into the kernel's + permission registry. On uninstall, they are removed. The admin assigns + them to groups in **Admin > Groups**. +- **gate_permission** — if set, the kernel checks this permission before + calling `on_request`. Unauthorized users get a 403 without the + extension code executing. + +In Starlark, check permissions inline via `req["permissions"]` or call +`permissions.check(user_id, "image-gen.use")`. + ## Starlark Sandbox API Starlark scripts run server-side with a 1M operation budget and no diff --git a/docs/STARLARK-REFERENCE.md b/docs/STARLARK-REFERENCE.md index e8edc11..8b916a1 100644 --- a/docs/STARLARK-REFERENCE.md +++ b/docs/STARLARK-REFERENCE.md @@ -37,6 +37,17 @@ val = settings.get("theme", "light") The cascade respects the `user_overridable` flag from the package manifest. See [Permissions & Groups](PERMISSIONS-AND-GROUPS) for details. +```python +# Check if a runtime capability is available +if settings.has_capability("pgvector"): + # Use native vector search + ... +``` + +`has_capability(name)` returns `True` if the named environment capability +is detected by the kernel. Detected capabilities: `pgvector`, `workspace`, +`object_storage`, `s3`, `postgres`. + ### lib Load exported functions from library packages. @@ -53,6 +64,20 @@ Requirements: - Results are cached per execution (calling `require` twice returns the same object). +### permissions + +Check whether a user has a specific permission. + +```python +if permissions.check(user_id, "image-gen.use"): + # User is authorized + ... +``` + +Returns `True` if the user has the permission, `False` otherwise (including +when the user is not found). Resolves the user's groups and merges granted +permissions — works for both kernel and extension-declared permissions. + ## Permission-gated modules These modules are only available if the package has the corresponding @@ -344,6 +369,41 @@ files.delete_prefix("temp/") --- +### workspace + +**Permission:** `workspace.manage` + +Managed disk directories for extensions that need a real filesystem +(git clones, compilers, media tools). Each workspace is scoped to +`{WORKSPACE_ROOT}/{packageID}/{name}/`. + +```python +# Create a workspace (idempotent) +path = workspace.create("my-repo") +# Returns the absolute path to the directory + +# Get the path (None if workspace doesn't exist) +path = workspace.path("my-repo") + +# List all workspaces owned by this extension +names = workspace.list() # ["my-repo", "cache"] + +# Delete a workspace and all its contents +workspace.delete("my-repo") + +# Get disk usage in bytes (10-second timeout) +size = workspace.usage("my-repo") # 1048576 +``` + +**Constraints:** +- Names must match `^[a-z][a-z0-9_]{0,62}$` (lowercase, no spaces or separators). +- Path traversal and symlink escape are blocked. +- Quota enforcement via `WORKSPACE_QUOTA_MB` env var (0 = unlimited). +- Module not available if `WORKSPACE_ROOT` is unset or not writable. + Use `settings.has_capability("workspace")` to check availability. + +--- + ## Example: automated stage hook A simple hook that reads a setting, queries data, and advances: diff --git a/server/pages/templates/base.html b/server/pages/templates/base.html index 20a28fe..8e5cf16 100644 --- a/server/pages/templates/base.html +++ b/server/pages/templates/base.html @@ -29,7 +29,7 @@ diff --git a/server/pages/templates/surfaces/team-admin.html b/server/pages/templates/surfaces/team-admin.html index 8a89b8d..55d3396 100644 --- a/server/pages/templates/surfaces/team-admin.html +++ b/server/pages/templates/surfaces/team-admin.html @@ -6,7 +6,7 @@ {{define "surface-team-admin"}}
-
+
Loading team admin…
{{end}} diff --git a/server/pages/templates/surfaces/welcome.html b/server/pages/templates/surfaces/welcome.html index a77f023..b2a3ca4 100644 --- a/server/pages/templates/surfaces/welcome.html +++ b/server/pages/templates/surfaces/welcome.html @@ -4,7 +4,7 @@ */}} {{define "surface-welcome"}} -
+
{{end}} {{define "scripts-welcome"}} diff --git a/src/css/extension-surface.css b/src/css/extension-surface.css index dbcfbe1..337597c 100644 --- a/src/css/extension-surface.css +++ b/src/css/extension-surface.css @@ -5,7 +5,8 @@ .extension-surface { display: flex; flex-direction: column; - height: 100%; + flex: 1; + min-height: 0; overflow: hidden; } diff --git a/src/css/surfaces.css b/src/css/surfaces.css index 37f9c99..d59a7bb 100644 --- a/src/css/surfaces.css +++ b/src/css/surfaces.css @@ -22,13 +22,13 @@ /* ── Docs Surface ────────────────────────── */ .surface-docs { - display: flex; flex-direction: column; height: 100%; overflow: hidden; + display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; } /* ── Settings Surface ────────────────────── */ .surface-settings { - display: flex; height: 100%; overflow: hidden; + display: flex; flex: 1; min-height: 0; overflow: hidden; } .settings-topbar { display: flex; align-items: center; gap: var(--sp-3); @@ -89,7 +89,7 @@ /* ── Admin Surface ───────────────────────── */ .surface-admin { - display: flex; flex-direction: column; height: 100%; overflow: hidden; + display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; } .admin-topbar { display: flex; align-items: center; gap: var(--sp-3); @@ -168,7 +168,7 @@ /* ── Editor Surface ──────────────────────── */ .surface-editor { - display: flex; flex-direction: column; height: 100%; overflow: hidden; + display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; } .editor-topbar { display: flex; align-items: center; gap: var(--sp-2);