Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
1593 lines
77 KiB
Markdown
1593 lines
77 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to Armature are documented here.
|
||
|
||
## v0.6.9 — Session Lifetime Config
|
||
|
||
Admin-configurable session durations, "keep me logged in" opt-in, and
|
||
optional idle timeout. Completes the auth hardening started in v0.6.8.
|
||
|
||
### Added
|
||
|
||
- **Admin session settings**: `session.access_token_ttl` (default 15m,
|
||
clamp 5m–60m) and `session.refresh_token_ttl` (default 7d, clamp
|
||
1h–90d) stored in `global_settings`. New `LoadSessionConfig()` helper
|
||
reads and clamps values with `parseDurationString()` supporting `m`,
|
||
`h`, `d` suffixes.
|
||
- **"Keep me logged in" checkbox**: Login form opt-in. Checked = full
|
||
`refresh_token_ttl`. Unchecked = capped at 24h. Cookie `max-age`
|
||
tracks whichever lifetime was chosen. `keep_login` flag stored on
|
||
refresh token row.
|
||
- **Config-driven token generation**: `generateTokens()` reads TTLs from
|
||
`global_settings` instead of hardcoded `15*time.Minute` /
|
||
`7*24*time.Hour`. Response includes `expires_in` and
|
||
`refresh_expires_in` (seconds) so the client schedules refresh
|
||
correctly.
|
||
- **Idle timeout (optional)**: Admin-toggleable (default off). Server
|
||
checks `last_activity_at` on refresh-token row; rejects if gap exceeds
|
||
`session.idle_timeout`. Client SDK pings `POST /api/v1/auth/activity`
|
||
on click/keydown (debounced, max 1/min).
|
||
- **Admin Settings > Session section**: Dropdowns for access TTL, refresh
|
||
TTL, and idle timeout with toggle.
|
||
- **7 new tests**: Duration parsing, clamping (low/high), defaults,
|
||
invalid values, empty idle timeout.
|
||
|
||
### Changed
|
||
|
||
- `CreateRefreshToken` store method now accepts `keepLogin bool`
|
||
parameter; both Postgres and SQLite implementations updated.
|
||
- `GetRefreshTokenInfo` returns `RefreshTokenInfo` struct with
|
||
`UserID`, `KeepLogin`, `LastActivityAt` for idle-timeout decisions.
|
||
- SDK `auth.login()` accepts optional third `keepLogin` parameter;
|
||
cookie max-age derived from server `refresh_expires_in` response.
|
||
- SDK boots activity tracking after successful auth boot.
|
||
|
||
## v0.6.8 — Cookie Fix + UI Hardening Roadmap
|
||
|
||
### Fixed
|
||
|
||
- **Session cookie max-age bug**: `arm_token` cookie was set to 15 min
|
||
(matching access token) while refresh token lasted 7 days. Cookie now
|
||
matches refresh token lifetime so Go SSR middleware can serve page
|
||
shells while JS refreshes the access token client-side.
|
||
|
||
### Added
|
||
|
||
- **ROADMAP-UI.md**: Detailed UI hardening roadmap (v0.6.9–v0.6.15)
|
||
covering session config, viewport foundation, CSS deduplication,
|
||
extension CSS isolation, responsive layout, visual polish, and
|
||
automated usability survey gate.
|
||
|
||
## v0.6.7 — Native mTLS
|
||
|
||
End-to-end mutual TLS without a reverse proxy. Targets systemd+podman
|
||
deployments where the Go binary terminates TLS itself.
|
||
|
||
### Added
|
||
|
||
- **`TLS_MODE` config**: Three values — `none` (default, plain HTTP), `server`
|
||
(TLS, no client cert), `mtls` (mutual TLS, client cert required). Independent
|
||
of `AUTH_MODE`. Combinations: `server`+`builtin` for HTTPS with password auth,
|
||
`mtls`+`mtls` for full mTLS identity, `none`+`mtls` for proxy-terminated
|
||
(existing behavior).
|
||
- **TLS server mode**: Binary calls `ListenAndServeTLS` directly when
|
||
`TLS_MODE` is `server` or `mtls`. TLS 1.3 minimum, no cipher suite
|
||
configuration. Config: `TLS_CERT`, `TLS_KEY`, `TLS_CA` path env vars.
|
||
- **`MTLSNativeProvider`**: New auth provider that reads
|
||
`r.TLS.PeerCertificates[0]` directly — no header trust. `Subject.CommonName`
|
||
becomes username, `sha256(cert.Raw)` becomes `external_id`. Auto-provisions
|
||
users identically to the proxy provider. Selected when `AUTH_MODE=mtls` and
|
||
`TLS_MODE=mtls`.
|
||
- **Shared mTLS helpers**: `ParseDN()`, `FingerprintCert()`, and
|
||
`resolveOrProvision()` extracted to `mtls_helpers.go`. Both proxy and native
|
||
providers consume the same user resolution logic.
|
||
- **Peer TLS config**: `BuildPeerTLSConfig()` constructs a `*tls.Config` for
|
||
outbound node-to-node connections (forward-looking — cluster registry is
|
||
currently DB-backed with no HTTP peer calls).
|
||
- **`armature-ca.sh`**: Shell wrapper around openssl for cert provisioning.
|
||
Three commands: `init` (CA keypair), `issue-node` (365d, ServerAuth +
|
||
ClientAuth EKU), `issue-user` (90d, ClientAuth only). All ECDSA P-256, PEM
|
||
output.
|
||
- **12 new tests**: `FingerprintCert` determinism, native provider unit tests
|
||
(nil TLS, empty certs, no CN, email extraction), TLS integration tests (no
|
||
cert rejected, wrong CA rejected, expired cert rejected, valid cert accepted,
|
||
peer certificate visibility).
|
||
|
||
### Changed
|
||
|
||
- **`MTLSProvider` → `MTLSProxyProvider`**: Renamed for clarity. Config type
|
||
`MTLSConfig` → `MTLSProxyConfig`. Constructor `NewMTLSProvider` →
|
||
`NewMTLSProxyProvider`. File `mtls.go` → `mtls_proxy.go`.
|
||
|
||
## v0.6.6 — Final Hardening
|
||
|
||
Final pass before public release. Security, correctness, and developer
|
||
experience. No new features — only fixes, validation, and cleanup.
|
||
|
||
### Added
|
||
|
||
- **`ValidateManifest()` gate**: Centralized manifest validation function
|
||
(`package_validate.go`) called at both upload-install and bundled-install
|
||
time. Catches malformed manifests early with clear error messages. 12 unit
|
||
tests covering all type constraints and edge cases.
|
||
- **Extension dependency auto-activation**: Installing a package whose
|
||
`dependencies` list an uninstalled library will auto-install that library
|
||
from the bundled packages directory. If the dependency is not bundled, the
|
||
error message lists exactly what's missing.
|
||
- **`OptionalAuth` middleware**: New page middleware for workflow visitor
|
||
routes. Authenticates if a token is present, allows anonymous pass-through
|
||
otherwise. Replaces the stale `AuthOrRedirect` TODO for Session routes.
|
||
- **Package signing schema reservation**: `signature` field accepted in
|
||
manifest (no-op). `PACKAGE_VERIFY_SIGNATURES` env var logs warnings for
|
||
unsigned packages when enabled. No cryptographic verification yet — schema
|
||
slot reserved to prevent a breaking change later.
|
||
- **ICD/SDK runner v0.6.x coverage**: Smoke tier adds metrics, cluster,
|
||
backups, docs, and OpenAPI JSON endpoints. SDK admin domain adds metrics,
|
||
cluster, and backups dual-path tests.
|
||
|
||
### Fixed
|
||
|
||
- **OIDC nonce validation**: ID token nonce claim is now validated against
|
||
the stored authorization request nonce. Previously the nonce was generated
|
||
and stored but never checked on callback — a token replay/substitution
|
||
vulnerability.
|
||
|
||
### Changed
|
||
|
||
- **Schema migration stub**: `RunSchemaMigrations()` no longer pretends to
|
||
work. Replaced with a log-only function documenting that only additive
|
||
schema changes are supported. Downgrade rejection preserved.
|
||
- **Session middleware TODO resolved**: `main.go` Session slot now uses
|
||
`OptionalAuth` instead of `AuthOrRedirect`. OIDC nonce TODO and migration
|
||
stub TODO also resolved.
|
||
|
||
## v0.6.5 — Renderer Pipeline + Docs Rewrite
|
||
|
||
Lifts block rendering to a kernel SDK primitive so all surfaces share one
|
||
markdown pipeline. Rewrites docs for external audience. Adds CONTRIBUTING
|
||
guide and extension tutorial.
|
||
|
||
### Added
|
||
|
||
- **`sw.renderers` SDK module**: Kernel-level renderer registry. Extensions
|
||
register block renderers (fenced code blocks) and post renderers (DOM
|
||
post-processing) once; all surfaces consume via `sw.markdown`.
|
||
- **`sw.markdown` SDK module**: Unified markdown rendering via `marked` v16
|
||
(vendored). Lazy-loads `marked` + DOMPurify. Custom code hook delegates
|
||
fenced blocks to `sw.renderers`. Wikilink tokenizer (`[[Page Name]]`)
|
||
built in for all surfaces.
|
||
- **Browser extension script loader**: Server injects `<script>` tags for
|
||
enabled browser-tier extensions into all pages. Extensions register via
|
||
`sw:ready` event.
|
||
- **Chat markdown support**: Messages with `content_type: 'markdown'` render
|
||
via `sw.markdown`. Existing `text` messages stay plain (no behavior change).
|
||
- **Mermaid architecture diagrams**: Six diagrams in `docs/ARCHITECTURE.md`:
|
||
system overview, request flow, extension lifecycle, realtime events,
|
||
settings cascade, cluster topology.
|
||
- **`CONTRIBUTING.md`**: Development setup, project structure, test commands,
|
||
code conventions.
|
||
- **`docs/TUTORIAL-FIRST-EXTENSION.md`**: Step-by-step guide to building and
|
||
installing a browser extension.
|
||
- **`docs/DESIGN-WORKFLOWS.md`**: Clean workflow system design document.
|
||
- **13 new renderers tests** (`src/js/__tests__/renderers.test.js`).
|
||
|
||
### Changed
|
||
|
||
- **Notes surface**: Hand-rolled markdown renderer replaced with
|
||
`sw.markdown.renderSync()`. Post-renderers (mermaid, katex, etc.) now
|
||
execute on preview. ~90 lines deleted.
|
||
- **Docs surface**: Hand-rolled markdown renderer replaced with
|
||
`sw.markdown.renderSync()`. ~160 lines deleted.
|
||
- **4 renderer extensions** (mermaid, katex, csv-table, diff-viewer):
|
||
Rewritten from dead `Extensions.register()` pattern to IIFE +
|
||
`sw.renderers.register()` via `sw:ready` event. All rendering logic
|
||
preserved.
|
||
- **Surface alias routes removed**: Six `/api/v1/admin/surfaces/*` backward
|
||
compat routes deleted from `main.go`. SDK and ICD runner migrated to
|
||
canonical `/api/v1/admin/packages/*` endpoints.
|
||
- **Docs rewritten for external audience**: All references to fork history,
|
||
internal codenames, and pre-fork version numbers removed from docs,
|
||
CHANGELOG, and ROADMAP.
|
||
|
||
### Removed
|
||
|
||
- `docs/DESIGN-WORKFLOW-REDESIGN-0.2.6.md` — replaced by `DESIGN-WORKFLOWS.md`.
|
||
- Hand-rolled markdown renderers in Notes and Docs surfaces.
|
||
- Six `/admin/surfaces/*` API alias routes.
|
||
|
||
---
|
||
|
||
## v0.6.4 — Admin Health/Metrics Tab + Cluster Merge
|
||
|
||
Structural consolidation: cluster dashboard merges into Admin as a Health tab.
|
||
Comprehensive metrics endpoint for runtime, DB, cluster, and extension stats.
|
||
|
||
### Added
|
||
|
||
- **Admin Health tab**: New "Health" section under Monitoring in the Admin
|
||
surface. Auto-refreshing panels for runtime, database, cluster, and extension
|
||
metrics with configurable poll interval (5/10/30/60s).
|
||
- **`GET /api/v1/admin/metrics`**: Single JSON endpoint returning all platform
|
||
metrics — runtime (goroutines, heap, GC, uptime, FDs), DB pool (latency,
|
||
active/idle/max, PG-only dead tuples + active backends), cluster nodes
|
||
(when PG multi-node), and extension stats (Starlark exec/errors/duration,
|
||
trigger fires, event bus publish/deliver counts).
|
||
- **Fattened heartbeat payload**: Cluster heartbeat JSONB now carries stack
|
||
usage, GC CPU%, extensions loaded, Starlark execution counters, and trigger
|
||
fire count — visible in per-node cluster cards.
|
||
- **Sandbox execution tracking**: `ExecPackage` and `CallEntryPoint` now
|
||
increment atomic counters (exec, errors, duration) and the previously
|
||
declared but unused `SandboxExecutionsTotal` Prometheus counter.
|
||
- **Event bus counters**: `Bus.PublishCount()` and `Bus.DeliverCount()` track
|
||
cumulative publish/deliver operations.
|
||
- **Trigger fire counter**: `Engine.FireCount()` tracks cumulative trigger
|
||
fires across webhook, event, and scheduled triggers.
|
||
- 4 new handler tests (metrics SQLite shape, cluster shape, extension counters).
|
||
- 1 new cluster test (fattened heartbeat payload).
|
||
|
||
### Changed
|
||
|
||
- **Health endpoint consolidation**: `/health` and `/api/v1/health` now use a
|
||
shared `buildHealthResponse()` function returning identical JSON. Both now
|
||
include `registration_enabled` when the database is connected.
|
||
- **Block renderer `requires` removed**: `mermaid-renderer`, `katex-renderer`,
|
||
`csv-table`, and `diff-viewer` no longer require `["chat"]` — they activate
|
||
on any surface (notes, docs, etc.).
|
||
|
||
### Removed
|
||
|
||
- **`cluster-dashboard` package**: Standalone surface package retired. Cluster
|
||
node visibility is now in Admin > Monitoring > Health.
|
||
|
||
---
|
||
|
||
## v0.6.3 — Dead Code Sweep + Registry Fix
|
||
|
||
Hardening: fix broken registry install, add registry settings UI,
|
||
clean up dead code, narrow default bundle.
|
||
|
||
### Fixed
|
||
|
||
- **Registry install**: SDK was sending `{ url }` but the Go handler expects
|
||
`{ download_url }`, causing every registry Install click to return 400.
|
||
|
||
### Added
|
||
|
||
- **Package Registry settings**: New "Package Registry" section in Admin >
|
||
Settings with a URL input field for configuring the external registry.
|
||
- **`scripts/generate-registry.sh`**: Shell script that reads `.pkg` ZIPs,
|
||
extracts manifests, and emits a `registry.json` for self-hosted discovery.
|
||
- **`docs/PACKAGE-REGISTRY.md`**: Documents registry JSON format, admin
|
||
configuration, script usage, and self-hosting setup.
|
||
|
||
### Removed
|
||
|
||
- **Dead Go code**: Orphaned channel/message type comments from
|
||
`interfaces.go`, unused `roleFilterType()` from `pages.go`, stale version
|
||
comment from `main.go`.
|
||
- **Dead vendor JS**: `marked.min.js` (40KB) and `purify.min.js` (22KB) —
|
||
unused vendor copies with zero production imports. Removed cache entries
|
||
from `sw.js`.
|
||
- **`dev.html`**: 676-line standalone dev gallery, not referenced by anything.
|
||
- **Stale version comments**: Stripped legacy version annotations across
|
||
~140 files — standalone comments, inline suffixes, section headers, and
|
||
config field docs. Reworded to describe features rather than reference
|
||
obsolete internal version numbers.
|
||
|
||
### Changed
|
||
|
||
- **Default bundle narrowed** from 10 packages to 5: `notes`, `chat`,
|
||
`chat-core`, `mermaid-renderer`, `schedules`. All other packages remain
|
||
available via `BUNDLED_PACKAGES=*` or explicit list.
|
||
|
||
---
|
||
|
||
## v0.6.2 — Docs Polish + Dynamic OpenAPI
|
||
|
||
Docs surface polish and dynamic OpenAPI spec with extension route merging.
|
||
|
||
### Added
|
||
|
||
- **Dynamic OpenAPI spec**: `GET /api/docs/openapi.json` builds a merged
|
||
spec at request time — kernel endpoints + auto-generated stubs for every
|
||
extension `api_routes` entry. Extensions may optionally declare `api_schema`
|
||
in `manifest.json` for richer path items (params, body, response examples).
|
||
- **`api_schema` manifest field**: Optional array in `manifest.json`. Malformed
|
||
entries are logged and skipped — never blocks extension loading.
|
||
- **Tests**: 7 new handler tests (zero extensions, stubs, rich schema,
|
||
multi-extension, malformed schema, disabled exclusion, required fields).
|
||
|
||
### Fixed
|
||
|
||
- **Dark mode contrast**: Replaced all hardcoded light-mode fallbacks in docs
|
||
CSS with theme-aware CSS variables. Tables, code blocks, nav items, and
|
||
headings now readable in dark mode.
|
||
- **Docs loading state**: Replaced borrowed `settings-placeholder` animation
|
||
with docs-specific pulse skeleton. Added error state with retry button.
|
||
- **Docs routing**: Default route (`/docs`) now redirects to
|
||
`/docs/GETTING-STARTED` instead of the blank `/:section` placeholder.
|
||
- **Docs scrolling**: Content area now scrolls correctly — full document
|
||
visible without text overflowing viewport.
|
||
- **Docs icon**: Changed from 🧩 to 📖 in the user menu.
|
||
- **Docs duplicate menu entry**: Docs was appearing twice (once from surfaces
|
||
API, once from hardcoded items). Added `'docs'` to `CORE_IDS` filter.
|
||
- **Swagger UI**: Updated to point at dynamic `/api/docs/openapi.json`
|
||
endpoint. Static YAML preserved for backward compatibility.
|
||
- **OpenAPI tests on Postgres**: `openapiTestStores()` was hardcoded to
|
||
`sqlite.NewStores()` — caused `pq: syntax error at or near ","` in CI.
|
||
Now uses `database.IsSQLite()` check like all other handler tests.
|
||
|
||
### API
|
||
|
||
| Endpoint | Method | Description |
|
||
|----------|--------|-------------|
|
||
| `/api/docs/openapi.json` | GET | Dynamic merged OpenAPI 3.0 spec |
|
||
|
||
---
|
||
|
||
## v0.6.1 — Backup/Restore + Documentation
|
||
|
||
Operational tooling for data safety and extension authoring.
|
||
|
||
### Added
|
||
|
||
- **Backup/Restore**: Full-instance backup as `.swb` ZIP archive containing
|
||
core tables (JSONL), ext_data tables, and package assets. Stream to client
|
||
or save server-side. Restore wipes and rebuilds from archive. Dialect-neutral
|
||
(works across SQLite and Postgres).
|
||
- **Admin backup section**: New "Backup" tab under `/admin` with create,
|
||
download, delete, and restore operations. Destructive restore requires
|
||
confirmation dialog.
|
||
- **Documentation surface**: Builtin surface at `/docs` with sidebar navigation
|
||
and client-side markdown rendering. Ships with 5 documents: Getting Started,
|
||
Extension Guide, API Reference, Deployment, Package Format.
|
||
- **Docs API**: `GET /api/v1/docs` lists available documents, `GET /api/v1/docs/:name`
|
||
returns raw markdown content. Authenticated (all users).
|
||
- **Tests**: 6 backup handler tests + E2E script (`ci/e2e-backup-test.sh`).
|
||
|
||
### API
|
||
|
||
| Endpoint | Method | Description |
|
||
|----------|--------|-------------|
|
||
| `/api/v1/admin/backup` | POST | Create backup (stream or `?store=true`) |
|
||
| `/api/v1/admin/backups` | GET | List server-side backups |
|
||
| `/api/v1/admin/backups/:name` | GET | Download backup |
|
||
| `/api/v1/admin/backups/:name` | DELETE | Delete backup |
|
||
| `/api/v1/admin/restore` | POST | Restore from `.swb` upload |
|
||
| `/api/v1/docs` | GET | List documentation |
|
||
| `/api/v1/docs/:name` | GET | Get document content |
|
||
|
||
---
|
||
|
||
## v0.6.0 — Cluster Registry + HA
|
||
|
||
MVP convergence point. PG-backed cluster registry for horizontal scaling —
|
||
zero new infrastructure (no etcd/Consul/Redis).
|
||
|
||
### Added
|
||
|
||
- **Cluster registry**: `node_registry` UNLOGGED table with self-registration
|
||
on startup (`INSERT ... ON CONFLICT DO UPDATE`), heartbeat tick (default 10s),
|
||
stale sweep (default 30s threshold), and self-eviction (`os.Exit(1)` when
|
||
swept by a peer — K8s restarts the process).
|
||
- **Cluster admin API**: `GET /api/v1/admin/cluster` returns all registered
|
||
nodes with runtime stats (goroutines, heap, GC, uptime, ws_clients) in the
|
||
standard `{data: [...]}` envelope.
|
||
- **Health endpoint cluster info**: `GET /health` and `GET /api/v1/health`
|
||
now include `node_id` and `cluster: {size, peers, heartbeat_age_ms}` when
|
||
running on Postgres with the registry active.
|
||
- **Cluster dashboard**: `cluster-dashboard` admin surface package renders one
|
||
card per node with live runtime stats. Auto-refreshes every 10s. Stats are
|
||
JSONB — new keys render automatically without schema migration.
|
||
- **Cluster config**: `CLUSTER_NODE_ID` (default hostname-PID),
|
||
`CLUSTER_HEARTBEAT_INTERVAL` (default 10s), `CLUSTER_STALE_THRESHOLD`
|
||
(default 30s), `CLUSTER_ENDPOINT` (Phase 2 mesh, auto-detect).
|
||
- **3-replica E2E test**: `docker-compose-e2e.yml` now runs 3 replicas.
|
||
`ci/e2e-cluster-test.sh` verifies registration, stale sweep on stop, and
|
||
re-registration on restart.
|
||
- **5 new tests**: 3 cluster registry unit tests (stats collection, start/stop
|
||
lifecycle, node ID) + 2 handler tests (list nodes, empty response).
|
||
- **Curated bundle expanded**: `chat` and `cluster-dashboard` added to the
|
||
default bundle (now 10 packages). Production deployments can override with
|
||
`BUNDLED_PACKAGES=notes,chat,chat-core,cluster-dashboard` for a lean set.
|
||
|
||
### Changed
|
||
|
||
- SQLite deployments are unaffected — cluster store is nil, all cluster code
|
||
is guarded behind `database.IsPostgres() && stores.Cluster != nil`.
|
||
Cluster dashboard gracefully shows "0 nodes registered" on SQLite.
|
||
|
||
## v0.5.5 — Upgrade Testing
|
||
|
||
### Fixed
|
||
|
||
- **Bundled permission auto-grant on Postgres**: The `InstallBundledPackages`
|
||
auto-grant SQL used SQLite-style `granted = 1` / `granted = 0` which fails
|
||
on Postgres BOOLEAN columns. Now dialect-aware: `true`/`false` on Postgres,
|
||
`1`/`0` on SQLite. This caused bundled extensions (notes, chat-core, etc.)
|
||
to install with permissions not granted on Postgres deployments.
|
||
- **E2E test API field corrections**: Fixed `ci/e2e-chat-test.sh` login field
|
||
(`"username"` → `"login"`), token extraction (`"token"` → `"access_token"`),
|
||
and profile endpoint (`/api/v1/me` → `/api/v1/profile`).
|
||
|
||
### Added
|
||
|
||
- **Upgrade test harness**: `docker-compose-upgrade.yml` + `ci/e2e-upgrade-test.sh`
|
||
orchestrate a full upgrade cycle: build old image, seed data (notes,
|
||
conversations, messages, settings), stop old, start new, verify data integrity
|
||
post-upgrade. Covers auth, notes, conversations, packages, and settings.
|
||
- **Rolling upgrade test**: `ci/e2e-upgrade-rolling.sh` tests rolling upgrade
|
||
with 2 replicas sharing Postgres. Upgrades one replica at a time, verifies
|
||
cross-replica reads during the mixed-version window.
|
||
- **11 new Go tests** in `upgrade_test.go`:
|
||
- Schema edge cases: add index (idempotent), add column (idempotent),
|
||
multi-column add, row preservation across migration.
|
||
- Settings migration: global/team/user overrides preserved across package
|
||
update, new keys get defaults, removed keys not deleted.
|
||
- Package compatibility: bundled skip-if-present on restart, dormant status
|
||
for unmet requires, enabled/type preserved across version bump.
|
||
- Direct `MigrateExtTables` tests: new table creation, column add with row
|
||
preservation, index idempotency.
|
||
|
||
## v0.5.4 — Package Updates
|
||
|
||
### Added
|
||
|
||
- **Package update API**: `POST /api/v1/admin/packages/:id/update` accepts a
|
||
`.pkg` archive, validates semver version bump (rejects same/older), applies
|
||
additive schema migration, merges settings, replaces assets, and re-syncs
|
||
permissions/triggers/dependencies.
|
||
- **Package export API**: `GET /api/v1/admin/packages/:id/export` streams the
|
||
installed package as a downloadable `.pkg` ZIP archive. Completes the manual
|
||
rollback story: export before update, re-install old `.pkg` if needed.
|
||
- **Semver parsing**: `ParseSemver` / `Compare` in `handlers/semver.go` with
|
||
prerelease support. Used by the update handler to enforce version bumps.
|
||
- **Additive schema migration**: `MigrateExtTables` diffs declared `db_tables`
|
||
against existing ext_data schema. Adds new columns (`ALTER TABLE ADD COLUMN`)
|
||
and new tables. No destructive changes — columns in DB but absent from
|
||
manifest are preserved. `ListExtColumns` introspects via `PRAGMA table_info`
|
||
(SQLite) or `information_schema` (Postgres).
|
||
- **Settings merge on update**: New manifest setting keys get their declared
|
||
default value; existing admin-configured values are preserved.
|
||
- **Admin UI Update button**: Non-core package cards now show an Update button
|
||
with a confirm dialog before upload.
|
||
- **14 new Go tests**: Version bump, 5 rejection cases (same/older/core/type/id
|
||
mismatch), schema add column with data preservation, schema add table,
|
||
settings merge, export with valid zip, default allowlist, wildcard allowlist.
|
||
|
||
### Changed
|
||
|
||
- **Curated default bundle**: Default bundled packages reduced from 23 to 8
|
||
core packages (notes, chat-core, workflow-chat, dashboard, 4 demo workflows).
|
||
`BUNDLED_PACKAGES=*` installs all; explicit comma-separated list for custom.
|
||
Existing deployments are unaffected (install-once, skip-if-present).
|
||
- **`BUNDLED_PACKAGES` env var**: Empty value now installs curated defaults
|
||
instead of all packages. Use `"*"` for previous install-all behavior.
|
||
|
||
## v0.5.3 — Chat Polish + Integration Testing
|
||
|
||
### Added
|
||
|
||
- **`db.query()` search_like**: New kernel-level `search_like` kwarg for text
|
||
search. Accepts `{col: pattern}` dict, generates OR-joined `LIKE` (SQLite) /
|
||
`ILIKE` (Postgres) clauses. Composes with existing `filters` via AND. Reusable
|
||
by any package. 5 new sandbox tests.
|
||
- **Conversation search**: `GET /search?q=term` endpoint in `chat-core`. Searches
|
||
conversation titles and message content, scoped to user's conversations.
|
||
Returns `{conversations: [...], messages: [...]}`.
|
||
- **Search UI**: Debounced search input in chat sidebar. Results view replaces
|
||
conversation list showing matched conversations and message snippets with
|
||
section headers. Click result to navigate, clear button to dismiss.
|
||
- **`workflow-chat` library package**: `on_advance` hook that creates scoped
|
||
group conversations when workflow stages require team collaboration. Adds all
|
||
team members as participants, sends system message linking to the instance,
|
||
enriches `stage_data` with `conversation_id`. Idempotent — skips if
|
||
conversation already exists.
|
||
- **Multi-user E2E test infrastructure**: `docker-compose-e2e.yml` with 2
|
||
replicas, Postgres, and nginx load balancer. `ci/e2e-chat-test.sh` shell
|
||
script covering auth, conversation CRUD, message send/read, cross-replica
|
||
consistency, search, and pagination. `ci/e2e-ws-listener.js` Node.js
|
||
WebSocket client for realtime event testing.
|
||
- **Workflow hook tests**: 6 new Go tests for `parseOnAdvanceResult` covering
|
||
None, nil, error, enriched stage_data, non-dict, and nested structure cases.
|
||
|
||
### Changed
|
||
|
||
- **Message pagination polish**: Scroll position now preserved when loading older
|
||
messages — records `scrollHeight` before prepend, restores via
|
||
`requestAnimationFrame`. Loading spinner shown at top of thread during fetch.
|
||
"Load older messages" button hidden while loading.
|
||
- **chat-core** bumped to v0.2.0 (new search endpoint).
|
||
- **chat** bumped to v0.2.0 (search UI, pagination polish).
|
||
|
||
## v0.5.2 — Chat Surface
|
||
|
||
### Added
|
||
|
||
- **`chat` surface package**: Full messaging UI built on `chat-core` library.
|
||
Route `/s/chat`, icon `💬`, depends on `chat-core`.
|
||
- **Conversation list sidebar**: Conversations sorted by recent activity, last
|
||
message preview, relative timestamps, unread count badges. "New" button
|
||
for conversation creation.
|
||
- **Message thread**: Paginated message history with cursor-based load-more on
|
||
scroll-to-top. Participant avatars, display names, timestamps. Scroll-to-
|
||
bottom on new messages.
|
||
- **Compose bar**: Auto-resizing textarea, Enter-to-send, Shift+Enter for
|
||
newline. Typing indicator broadcast on keypress (3s debounce).
|
||
- **Participant sidebar**: Collapsible right panel with participant list, online
|
||
status via presence hub, role badges. Add participant via `sw.ui.Dialog` +
|
||
`sw.ui.UserPicker`. Remove participant with confirmation.
|
||
- **Create conversation dialog**: Group or Direct Message type selector. Multi-
|
||
user picker with chip display for group, single picker for DM. Auto-title
|
||
from participant names.
|
||
- **Typing indicators**: Realtime broadcast via `POST /typing/:id` Starlark
|
||
endpoint. Frontend subscribes to `conversation:{id}` channel, shows
|
||
"X is typing..." with 4s auto-expire. Handles multiple typers.
|
||
- **Read receipts**: Auto mark-read on conversation focus and new incoming
|
||
messages. Unread counts in conversation list, cleared on selection.
|
||
- **Message editing**: Inline edit mode on own messages with textarea. Save on
|
||
Enter, cancel on Escape. "(edited)" label on edited messages. Realtime
|
||
propagation to other participants.
|
||
- **Message deletion**: Confirm dialog before delete. "This message was deleted"
|
||
placeholder. Admin can delete any message. Realtime propagation.
|
||
|
||
### Fixed
|
||
|
||
- **Stale token login deadlock**: REST client's 401→refresh→retry loop caused
|
||
a deadlock when the refresh endpoint itself returned 401 (stale DB). Auth
|
||
endpoints (`/auth/login`, `/auth/refresh`, `/auth/register`) are now excluded
|
||
from the retry loop. Defensive 8s timeout on `auth.boot()` ensures login
|
||
page always renders even on unexpected hangs.
|
||
- **Bundled package permissions**: Bundled packages were installed with
|
||
`pending_review` status and `granted=0` permissions, requiring manual admin
|
||
approval. Bundled installer now auto-grants all declared permissions and
|
||
sets status to `active` on install.
|
||
- **Creator display name**: `chat-core.create()` now accepts
|
||
`creator_display_name` so the conversation creator shows a readable name
|
||
in the participant sidebar instead of a raw UUID.
|
||
- **Chat dark mode theming**: Replaced non-existent CSS variable names with
|
||
actual theme tokens from `variables.css`. Fixes invisible text and wrong
|
||
backgrounds in dark mode.
|
||
- **Dialog dropdown clipping**: UserPicker autocomplete dropdown in dialogs
|
||
(New Conversation, Add Participant) no longer clipped by `overflow: auto`
|
||
on `.sw-dialog__body`.
|
||
- **User menu at scaled UI**: Menu primitive now divides anchor coordinates
|
||
and viewport dimensions by `sw.shell.getScale()` to compensate for CSS
|
||
`transform: scale()` on `#surfaceInner`. Fixes menu clipping at 110%+.
|
||
|
||
### Changed
|
||
|
||
- **Named Docker volume**: `docker-compose.yml` switched from bind mount
|
||
(`./data:/data`) to named volume (`sb_data:/data`). Fixes volume mount
|
||
failures when Docker daemon runs on a remote host (DinD, k8s pod).
|
||
Deterministic wipe via `docker compose down -v`.
|
||
|
||
## v0.5.1 — Chat Core Library
|
||
|
||
### Added
|
||
|
||
- **`chat-core` library package**: Conversations, messages, participants, and
|
||
read cursors as ext_data tables. Permissions: `db.write`, `realtime.publish`.
|
||
Consumable by other packages via `lib.require("chat-core")`.
|
||
- **4 ext_data tables**: `conversations` (title, type, created_by, updated_at),
|
||
`participants` (conversation_id, participant_id, type, display_name, role,
|
||
joined_at), `messages` (conversation_id, participant_id, content, content_type,
|
||
edited_at), `read_cursors` (conversation_id, participant_id,
|
||
last_read_message_id).
|
||
- **6 exported Starlark functions**: `create()`, `send()`, `history()`,
|
||
`add_participant()`, `remove_participant()`, `mark_read()`.
|
||
- **15 REST endpoints**: Full CRUD for conversations, messages, participants.
|
||
Cursor-based paginated message history. Per-conversation unread counts.
|
||
- **Realtime events**: `message`, `message.edited`, `message.deleted`,
|
||
`participant.added`, `participant.removed` published to
|
||
`conversation:{id}` channels.
|
||
- **`db.query()` range parameters**: New `before` and `after` kwargs accept
|
||
dicts of `{col: val}` generating `col < ?` / `col > ?` WHERE clauses.
|
||
Enables cursor-based pagination and efficient unread counting for all
|
||
extension packages. 7 new Go tests.
|
||
|
||
### Security
|
||
|
||
- **Invisible Unicode scanning gate** (GlassWorm / Trojan Source defense):
|
||
`ScanSource()` detects variation selectors, bidi overrides, zero-width chars,
|
||
tag characters, and other invisible Unicode across 10 ranges. `Verdict()`
|
||
blocks GlassWorm payloads (>=10 variation selector / tag chars), Trojan Source
|
||
(any bidi override), and excessive zero-width chars (>=6). Two enforcement
|
||
points: extension install (422 rejection before files written) and Starlark
|
||
execution (belt-and-suspenders). 23 new tests including GlassWorm payload
|
||
simulation.
|
||
|
||
### Changed
|
||
|
||
- **Admin packages type filter**: Added `Library` option alongside Surface/Extension/Full/Workflow. Filter dropdown replaced button group with themed `Dropdown` primitive (keyboard nav, CSS-var theming, consistent with rest of admin UI).
|
||
- SDK version bumped from 0.5.0 to 0.5.1.
|
||
|
||
## v0.5.0 — Realtime Primitive + Dialog Audit + Permissions UI
|
||
|
||
### Added
|
||
|
||
- **Realtime Starlark module**: `realtime.publish(channel, event, data)` lets
|
||
extensions publish events to WebSocket channels. Gated by the new
|
||
`realtime.publish` permission. Payloads limited to 7KB (pg_notify safe).
|
||
Reserved event prefixes (`system.`, `workflow.`, etc.) are blocked.
|
||
- **WS room protocol**: Clients send `room.subscribe` / `room.unsubscribe`
|
||
messages to join/leave rooms. Intercepted in the WebSocket readPump before
|
||
the bus publish gate (like ping). Per-connection cap of 100 rooms.
|
||
- **SDK realtime module**: `sw.realtime.subscribe(channel, [event], callback)`
|
||
manages room join/leave over WebSocket and filters incoming `realtime.*`
|
||
events by room. Returns unsubscribe handle. Auto re-joins all rooms on
|
||
WebSocket reconnect. `sw.realtime.channels()` debug helper.
|
||
- **Admin permissions UI**: Packages page gains a "Permissions" button for
|
||
packages with declared permissions. Inline drawer shows each permission with
|
||
Grant/Revoke toggle, granted-by user ID, and a "Grant All" bulk action.
|
||
- **Status badges**: `pending_review` (amber) and `suspended` (red) badges on
|
||
package rows. Enable toggle disabled with hint when `pending_review`.
|
||
- **SDK API client**: `permissions()`, `grantPerm()`, `revokePerm()`,
|
||
`grantAllPerms()` methods added to `sw.api.admin.packages`.
|
||
- **`starlarkToGoVal()` helper**: Converts Starlark values to Go `any` for
|
||
JSON marshaling (reverse of existing `goValToStarlark`).
|
||
- **8 new Go tests**: Route table cases for `realtime.*` and `room.*`,
|
||
realtime module publish (happy path, empty data, missing args, reserved
|
||
prefix, payload too large), `starlarkToGoVal` type conversion.
|
||
|
||
### Changed
|
||
|
||
- **Dialog audit**: 5 bare `confirm()` calls migrated to `await sw.confirm()`
|
||
with `{ destructive: true }` styling in tasks, schedules, notes (×2), and
|
||
editor packages. Package archives rebuilt.
|
||
- SDK version bumped from 0.2.3 to 0.5.0.
|
||
- Event route table: `realtime.` → DirToClient, `room.subscribe` /
|
||
`room.unsubscribe` → DirFromClient.
|
||
- Runner gains `bus` field and `SetBus()` method; wired in main.go.
|
||
|
||
## v0.4.7 — Note Graph
|
||
|
||
### Added
|
||
|
||
- **Graph API**: `GET /graph` endpoint returns all non-archived notes as nodes
|
||
and resolved wikilinks as edges in a single payload. Nodes include id, title,
|
||
folder_id, and tags. Edges include source, target, and link text.
|
||
- **Graph view**: Canvas-based force-directed graph visualization replaces the
|
||
editor pane when "Graph" button is clicked in the topbar. Force simulation
|
||
uses repulsion (all pairs), attraction (edges), and center gravity with
|
||
velocity damping. Supports zoom (scroll wheel) and pan (mouse drag).
|
||
- **Click focus**: Single-click a node to dim unconnected nodes/edges to 15%
|
||
opacity; clicked node and direct neighbors stay at full brightness. Edges
|
||
between focused nodes highlighted. Click empty space to reset.
|
||
- **Shift+click chain**: Hold shift and click a second node to union both
|
||
neighborhoods — trace thought paths across two hops.
|
||
- **Double-click open**: Double-click a graph node to navigate to that note in
|
||
the editor. Graph view closes, editor opens with the selected note.
|
||
- **Hover tooltips**: Hovering a node shows title, tag pills, and connection
|
||
count in an HTML overlay (no additional API calls).
|
||
- **Folder coloring**: Nodes colored by folder using a 10-color palette.
|
||
Unfiled notes shown in gray.
|
||
- **Tag filter integration**: Active tag filter dims non-matching nodes to 30%
|
||
opacity, stacking with click focus.
|
||
- **Orphan highlighting**: Notes with zero connections shown with dashed stroke.
|
||
"Hide orphans" checkbox in toolbar to toggle visibility.
|
||
|
||
### Changed
|
||
|
||
- Notes package version bumped from 0.7.0 to 0.8.0.
|
||
- Topbar gains a "Graph" toggle button alongside "New Note" and "Import .md".
|
||
|
||
|
||
## v0.4.6 — Sidebar Restructure
|
||
|
||
### Added
|
||
|
||
- **Sidebar tabs**: Two-tab header ("Notes" / "Outline") replaces the static
|
||
sidebar header. Notes tab contains folder tree, tag filter, search, and note
|
||
list. Outline tab shows the document heading tree for the active note.
|
||
Outline tab disabled (grayed) when no note is selected.
|
||
- **Sidebar outline**: Document outline relocated from editor-side panel into
|
||
the sidebar's Outline tab. Nested heading hierarchy (H1→H2→H3) with depth
|
||
indentation via `padding-left`. Click heading scrolls to it in both preview
|
||
and CM6 editor modes.
|
||
- **Active heading tracking**: Scroll listener (RAF-throttled) highlights the
|
||
current heading in the sidebar outline based on scroll position. Uses
|
||
`getBoundingClientRect()` in preview/split mode and CM6 viewport line in
|
||
edit mode.
|
||
|
||
### Changed
|
||
|
||
- Notes package version bumped from 0.6.0 to 0.7.0.
|
||
- `EditorPane` exposes headings, scroll function, and active heading index to
|
||
parent via callback props (`onHeadingsChange`, `scrollToHeadingRef`,
|
||
`onActiveHeadingChange`).
|
||
- Sidebar auto-resets to Notes tab when active note is deleted.
|
||
|
||
### Removed
|
||
|
||
- Old `DocumentOutline` component and `.doc-outline` CSS styles (replaced by
|
||
sidebar outline).
|
||
|
||
## v0.4.5 — Editor Modes + Document Outline
|
||
|
||
### Added
|
||
|
||
- **Tri-state view mode**: Notes open in rendered (read-only) mode by default.
|
||
Toolbar button cycles through Edit (CM6) and Split (side-by-side) modes.
|
||
Replaces the old binary preview toggle.
|
||
- **Split view**: Side-by-side CM6 editor + rendered preview using CSS grid.
|
||
CM6 instance preserved when toggling between edit and split modes.
|
||
- **Synced scroll**: In split mode, scrolling the CM6 editor proportionally
|
||
scrolls the preview pane via linear ratio mapping on `scrollDOM`.
|
||
- **View mode persistence**: User's preferred mode saved to localStorage
|
||
(`sw.storage.local('notes')`). New `editor_mode` manifest setting with
|
||
admin-level default (rendered / edit / split).
|
||
- **Document outline**: Collapsible TOC panel adjacent to the editor body.
|
||
`parseHeadings()` extracts heading level, text, and line number (skips
|
||
fenced code blocks). Click heading to scroll — uses CM6
|
||
`EditorView.scrollIntoView()` in edit/split mode, DOM `scrollIntoView()`
|
||
in rendered mode. Updates on body change with 300ms debounce.
|
||
|
||
### Changed
|
||
|
||
- Notes package version bumped from 0.5.0 to 0.6.0.
|
||
- Editor body wrapped in `.notes-editor__content` flex container to
|
||
accommodate the outline panel.
|
||
- Wikilink click handling active in both rendered and split modes.
|
||
- Mobile: split view collapses to single column, outline panel hidden.
|
||
|
||
---
|
||
|
||
## v0.4.4 — Rich Editor + Import/Export
|
||
|
||
### Added
|
||
|
||
- **CodeMirror 6 integration**: Notes editor now uses the vendored CM6 bundle
|
||
(`CM.noteEditor()`) for rich markdown editing with syntax highlighting,
|
||
wikilink autocomplete (`[[` triggers note title completion), and inline
|
||
preview decorations for headings, code blocks, and blockquotes.
|
||
- **CM6 dynamic loading**: Bundle loaded via `<script>` tag at boot time.
|
||
Falls back to plain textarea if CM6 is unavailable.
|
||
- **Export as .md**: Export button in editor header downloads the note as a
|
||
Markdown file with YAML frontmatter (title, tags, created date).
|
||
- **Import .md**: Import button in topbar allows uploading `.md`/`.markdown`/`.txt`
|
||
files. Parses YAML frontmatter for title and tags, creates note via API.
|
||
- **CSS for CM6**: Editor fills the container with `max-height: none` override
|
||
and proper scroller padding.
|
||
|
||
### Changed
|
||
|
||
- Notes package version bumped from 0.4.0 to 0.5.0.
|
||
- `EditorPane` component refactored to support CM6 with mutable refs for
|
||
title/body (required for CM6 callback closures).
|
||
- Ctrl/Cmd+S save works in both CM6 and textarea fallback modes.
|
||
|
||
---
|
||
|
||
## v0.4.3 — Backlinks + Wikilinks
|
||
|
||
### Added
|
||
|
||
- **Links table** (`ext_notes_links`): source_id, target_id, link_text columns
|
||
with indexes on source_id and target_id for bidirectional lookup.
|
||
- **Wikilink extraction**: `_extract_wikilinks()` parses `[[...]]` patterns
|
||
using `split("[[")` + `find("]]")` (Starlark has no regex or while loops).
|
||
Returns deduplicated list of link text strings.
|
||
- **Link sync on save**: `_sync_links()` called from `_create_note` and
|
||
`_update_note` when body changes. Follows the tags delete-all + reinsert
|
||
pattern. Resolves link text to note IDs via case-insensitive title matching.
|
||
Unresolved links stored with empty `target_id`.
|
||
- **Cascade delete**: Hard-deleting a note removes both outgoing links
|
||
(source_id matches) and incoming backlinks (target_id matches).
|
||
- **Link API endpoints**: `GET /links/:note_id` returns outgoing links.
|
||
`GET /backlinks/:note_id` returns incoming links enriched with source note
|
||
titles to avoid extra frontend round-trips.
|
||
- **Wikilink preview rendering**: `[[Note Title]]` syntax renders as clickable
|
||
accent-colored links in the markdown preview. Unresolved wikilinks styled
|
||
in danger/red with dashed underline.
|
||
- **Wikilink click navigation**: Clicking a resolved wikilink navigates to the
|
||
target note. Clicking an unresolved wikilink creates the note and navigates
|
||
to it, then re-saves the source note so the link resolves to blue.
|
||
- **Backlinks panel**: Collapsible panel below the editor showing all notes
|
||
that link to the current note. Each backlink displays source title and link
|
||
text. Click to navigate. Hidden when no backlinks exist.
|
||
- **Updated stats**: `/stats` response now includes `links` count.
|
||
|
||
### Data model
|
||
|
||
| Table | Columns |
|
||
|-------|---------|
|
||
| `ext_notes_links` | source_id, target_id, link_text |
|
||
|
||
### Notes package version
|
||
|
||
0.3.0 → 0.4.0
|
||
|
||
---
|
||
|
||
## v0.4.2 — Tags + Search
|
||
|
||
### Added
|
||
|
||
- **Tags table** (`ext_notes_tags`): note_id, tag columns with indexes on
|
||
both for bidirectional lookup. Many-to-many relationship via join table.
|
||
- **Tag CRUD API**: 3 new Starlark handlers — list all unique tags, get tags
|
||
for a note, replace tags for a note (delete-all + reinsert). Tags are
|
||
normalized to lowercase, trimmed, and deduplicated on save.
|
||
- **Tags in list/get/search**: `_list_notes` batch-fetches all tags in a
|
||
single query and attaches a `tags` array to each note item. `_get_note`
|
||
includes tags. `_search_notes` now matches search terms against tags.
|
||
- **Tag cascade delete**: Hard-deleting a note also removes its tag rows.
|
||
- **Tag input in editor**: `TagInput` component with removable pills, text
|
||
input (comma or Enter to add), and autocomplete dropdown populated from
|
||
all existing tags.
|
||
- **Tag pills on note cards**: `NoteCard` renders up to 3 tag pills with
|
||
"+N" overflow indicator for notes with many tags.
|
||
- **Tag filter in sidebar**: `TagFilter` component displays all unique tags
|
||
as clickable pills. Clicking toggles client-side filtering of the note
|
||
list by that tag.
|
||
- **Drag-and-drop note moves**: `NoteCard` is draggable (HTML5 drag/drop).
|
||
`FolderNode`, "All Notes", and "Unfiled" items are drop targets. On drop,
|
||
calls the existing move-note API.
|
||
- **Folder context menu**: Replaced `prompt()` hack with a proper right-click
|
||
popup menu showing "Add subfolder", "Rename", and "Delete" actions. Positioned
|
||
at click coordinates, dismissed on outside click.
|
||
- **Updated stats**: `/stats` response now includes `tags` count (unique).
|
||
|
||
### Data model
|
||
|
||
| Table | Columns |
|
||
|-------|---------|
|
||
| `ext_notes_tags` | note_id, tag |
|
||
|
||
### Notes package version
|
||
|
||
Bumped from 0.2.0 → 0.3.0. 15 API routes (12 existing + 3 new).
|
||
|
||
## v0.4.1 — Folders + Navigation Tree
|
||
|
||
### Added
|
||
|
||
- **Folders table** (`ext_notes_folders`): name, parent_id, creator_id,
|
||
sort_order. Indexed on parent_id and creator_id. Nested hierarchy via
|
||
parent_id foreign key (empty string = root).
|
||
- **Folder CRUD API**: 5 new Starlark handlers — list, create, update, delete
|
||
folders plus a dedicated move-note endpoint (`POST /notes/move`).
|
||
- **Navigation tree**: `FolderTree` + `FolderNode` Preact components in the
|
||
sidebar. Flat API response built into a nested tree client-side via `useMemo`.
|
||
Expand/collapse toggles, depth-based indentation, inline rename via
|
||
right-click context menu.
|
||
- **Folder filtering**: Clicking a folder filters notes by `folder_id` query
|
||
param. "All Notes" clears the filter. "Unfiled" shows notes with empty
|
||
folder_id (client-side filter).
|
||
- **Editor folder select**: Dropdown in the editor header moves notes between
|
||
folders. Shows nested hierarchy with `└` prefix indentation.
|
||
- **Folder-aware note creation**: New notes inherit the active folder's ID.
|
||
- **Delete cascade**: Deleting a folder orphans its notes (moves to Unfiled)
|
||
and reparents child folders to the deleted folder's parent.
|
||
- **Updated stats**: `/stats` response now includes `unfiled` and `folders`
|
||
counts.
|
||
|
||
### Data model
|
||
|
||
| Table | Columns |
|
||
|-------|---------|
|
||
| `ext_notes_folders` | name, parent_id, creator_id, sort_order |
|
||
|
||
### Notes package version
|
||
|
||
Bumped from 0.1.0 → 0.2.0. 12 API routes (7 existing + 5 new).
|
||
|
||
## v0.4.0 — Notes Surface
|
||
|
||
### Added
|
||
|
||
- **Notes surface package** (`packages/notes/`): Obsidian-style markdown notes
|
||
rebuilt as a standard installable `.pkg` archive. Type `full`, tier `starlark`.
|
||
Zero kernel changes — proves the extension stack end-to-end.
|
||
- **Starlark backend** (`script.star`): 7 API routes — CRUD, search, stats.
|
||
Note bodies stored in `ext_notes_notes` TEXT columns. List endpoint returns
|
||
lightweight projections (no body); full content fetched on select.
|
||
- **Markdown editor**: Preact+htm frontend with sidebar note list, monospace
|
||
textarea, and toggle-able live preview. Inline markdown renderer (~100 lines)
|
||
covers headings, bold, italic, code blocks, links, lists, blockquotes, and HR.
|
||
- **Auto-save**: Debounced save (1s) with dirty/saved status indicator.
|
||
`Ctrl/Cmd+S` for force save. Tab key inserts two spaces.
|
||
- **Pin & archive**: Pin important notes to the top of the list. Archive
|
||
(soft-delete) via the delete button; hard delete with `?hard=1` query param.
|
||
- **Search**: Case-insensitive full-text search across title and body content.
|
||
Search bar in sidebar filters the note list in real-time.
|
||
- **Theme support**: All styles use CSS variables — light and dark themes work
|
||
out of the box. Responsive layout collapses to vertical on narrow viewports.
|
||
|
||
### Data model
|
||
|
||
| Table | Columns |
|
||
|-------|---------|
|
||
| `ext_notes_notes` | title, body, folder_id, creator_id, updated_at, pinned, archived |
|
||
|
||
Indexes on `folder_id`, `creator_id`, `pinned`, `updated_at`.
|
||
|
||
### Planned (v0.4.1–v0.4.4)
|
||
|
||
- Folders + navigation tree
|
||
- Tags + enhanced search
|
||
- Backlinks + `[[wikilinks]]`
|
||
- Rich editor (CodeMirror 6) + markdown import/export
|
||
|
||
## v0.3.8 — Distribution
|
||
|
||
### Added
|
||
|
||
- **Bundled packages**: Production Docker image now ships with 12 pre-built
|
||
`.pkg` archives (4 workflows, 3 surfaces, 1 full, 1 library, 2 test runners,
|
||
1 extension). Auto-installed on first boot via `InstallBundledPackages()`.
|
||
Install-once, skip-if-present — admin uninstalls are respected on restart.
|
||
- **Package allowlist** (`BUNDLED_PACKAGES`): Comma-separated list of package
|
||
IDs to install. Empty (default) installs all. Useful for Helm/K8s where
|
||
different environments need different packages.
|
||
- **Skip bundled** (`SKIP_BUNDLED_PACKAGES=true`): Disables auto-install
|
||
entirely. Intended for production until post-MVP packages are ready.
|
||
- **Builder image** (`Dockerfile.builder`): Pre-caches Go modules, Node
|
||
dependencies, and vendor lib tarballs for faster custom builds.
|
||
- **Distribution docs** (`docs/DISTRIBUTION.md`): Quick start, bundled
|
||
packages, builder image usage, custom build guide, production deployment
|
||
reference.
|
||
- **Migration 012**: Adds `bundled` to `packages.source` CHECK constraint
|
||
(both Postgres and SQLite).
|
||
- **K8s manifest**: `SKIP_BUNDLED_PACKAGES` and `BUNDLED_PACKAGES` env vars
|
||
added to `k8s/armature.yaml`.
|
||
- **Tests**: 6 handler tests (fresh install, skip existing, missing dir, empty
|
||
dir, dormant handling, allowlist filtering).
|
||
|
||
### Environment defaults
|
||
|
||
| Environment | `SKIP_BUNDLED_PACKAGES` | `BUNDLED_PACKAGES` |
|
||
|-------------|------------------------|--------------------|
|
||
| Dev (compose) | `false` | (empty = all) |
|
||
| Test (K8s) | `false` | (empty = all) |
|
||
| Prod (K8s) | `true` | — |
|
||
|
||
## v0.3.7 — Package Audit
|
||
|
||
### Added
|
||
|
||
- **Dormant status**: New `dormant` value in `packages.status` CHECK constraint
|
||
(both Postgres and SQLite). Installer auto-detects packages with unmet
|
||
`requires` entries and sets `status=dormant, enabled=false`. Enable endpoint
|
||
returns 409 for dormant packages.
|
||
- **Generalized requires check**: Any unmet requirement triggers dormant status,
|
||
not just specific values. Known capabilities checked against a whitelist;
|
||
anything unknown is unmet.
|
||
- **Admin UI indicators**: Dormant badge, disabled Enable button with tooltip,
|
||
Dormant stat card in packages admin view.
|
||
- **Tests**: 4 new handler tests covering dormant status, enable blocking,
|
||
surface exclusion, and admin list inclusion.
|
||
|
||
### Fixed
|
||
|
||
- **Manifest fixes**: Fixed 6 chat-extension manifests (`"name"` → `"title"`)
|
||
that were blocking install. Added explicit `"type": "surface"` to
|
||
hello-dashboard, icd-test-runner, sdk-test-runner.
|
||
- **Dashboard + Editor tagged dormant**: Both depend on removed `sw.*`
|
||
imperative SDK (`sw.tabs`, `sw.chat`, `sw.layout`, etc.) — render blank.
|
||
Tagged with `requires: ["legacy-sdk"]` so installer auto-sets dormant.
|
||
- **Dependency check**: Relaxed library dependency validation to allow
|
||
`pending_review` libraries (gitea-client declares permissions). Only
|
||
`suspended`/`dormant` libraries blocked.
|
||
|
||
### Package Audit Results
|
||
|
||
- **7 working surfaces**: schedules, tasks, team-activity-log, git-board,
|
||
hello-dashboard, icd-test-runner, sdk-test-runner
|
||
- **8 dormant**: 6 chat-dependent (csv-table, diff-viewer, js-sandbox,
|
||
katex-renderer, mermaid-renderer, regex-tester) + 2 legacy-sdk (dashboard,
|
||
editor)
|
||
- **1 library**: gitea-client (standalone, active)
|
||
|
||
## v0.3.6 — Example Workflows + Interactive Demo
|
||
|
||
### Added
|
||
|
||
- **Bug Report Triage** (`packages/bug-report-triage`): Public entry, progressive
|
||
fieldsets, severity-based branch routing, SLA timer (3600s on critical fix).
|
||
- **Employee Onboarding** (`packages/employee-onboarding`): Starlark automated
|
||
stages (`db.insert`, `notifications.send`), manager signoff gate with
|
||
`required_role`, rejection reroute. `script.star` with `on_provision` and
|
||
`on_welcome` hooks. Creates `provisions` and `onboarding_log` ext_data tables.
|
||
- **Content Approval** (`packages/content-approval`): Multi-party signoff (quorum
|
||
of 2), rejection reroute creating a review cycle loop.
|
||
- **Webhook Notifier** (`packages/webhook-notifier`): Starlark `http.post` with
|
||
`connections.get` fallback, delivery logging to `webhook_log` ext_data table.
|
||
- **Workflow Demo** surface (`packages/workflow-demo`): Interactive walkthrough
|
||
with workflow cards, stage flow diagrams, Starlark viewer, API curl examples,
|
||
and "Try It" buttons. Route: `/s/workflow-demo`.
|
||
- **Engine context fix**: `started_by` added to automated stage Starlark context
|
||
dict, enabling hooks to reference the workflow initiator.
|
||
- **SLA package installer**: `sla_seconds` field added to `workflowPkgStage`
|
||
struct and wired in both install and export paths.
|
||
- **Snapshot format fix**: `parseSnapshotStages` helper handles both wrapped
|
||
(`{"stages":[...]}`) and legacy flat array snapshot formats in the engine,
|
||
fixing `corrupt version snapshot` errors when starting published workflows.
|
||
- **Workflow adoption**: `POST /teams/:teamId/workflows/:id/adopt` claims a
|
||
global (team_id=NULL) workflow for a team. `GET /teams/:teamId/workflows/available`
|
||
lists adoptable workflows. `TeamID` added to `WorkflowPatch` model.
|
||
- **Tests**: 3 new engine tests (automated context, SLA install, manifest
|
||
roundtrip). Total: 142 handler tests passing.
|
||
- Per-package `README.md` files for all 5 new packages.
|
||
|
||
### Fixed
|
||
|
||
- **Demo surface**: Added `sw.userMenu` to topbar. Fixed workflow install
|
||
detection (was reading wrong response key). CSS uses theme variables for
|
||
dark mode support.
|
||
- **Admin teams tab**: Extracted `.data` from paginated response — was stuck
|
||
at loading because `setTeams` received the envelope object, not the array.
|
||
- **Team-admin workflows**: Added "Adopt Global" button to claim package-installed
|
||
workflows into a team scope. Workflow list now unwraps paginated responses.
|
||
|
||
## v0.3.5 — Settings Audit + ICD + Tests + Clone
|
||
|
||
### Added
|
||
|
||
- **Clone endpoint**: `POST /api/v1/workflows/:id/clone` deep-copies a workflow
|
||
and all its stages. Creates an inactive draft with "Copy of" prefix. Handles
|
||
slug collisions with auto-suffix. Team-scoped mirror at
|
||
`/api/v1/teams/:teamId/workflows/:id/clone`.
|
||
- **Integration tests**: 7 new engine-level tests in `workflow_engine_test.go`
|
||
covering full lifecycle, branch routing, public entry, signoff validation gate,
|
||
signoff rejection, cancel-clears-assignments, and error cases. Total: 28 tests.
|
||
- **Staleness timeout UI**: `staleness_timeout_hours` input added to workflow
|
||
editor in team-admin settings (was backend-only since v0.3.3).
|
||
- **Branch rules UI**: Collapsible JSON textarea in stage form for configuring
|
||
conditional routing rules (was backend-only since v0.3.2).
|
||
|
||
### Changed
|
||
|
||
- **ICD (OpenAPI spec)**: Fixed stale `Workflow` schema (added `description`,
|
||
`branding`, `is_active`, `on_complete`, `retention`, `staleness_timeout_hours`).
|
||
Fixed stale `Stage` schema (removed `history_mode`/`transition_rules`, updated
|
||
`stage_mode` enum to `[form, review, delegated, automated]`, added `audience`,
|
||
`stage_type`, `starlark_hook`, `branch_rules`, `stage_config`, `sla_seconds`,
|
||
`auto_transition`, `assignment_team_id`, `surface_pkg_id`).
|
||
Added `WorkflowInstance`, `WorkflowAssignment`, `WorkflowSignoff` schemas.
|
||
Added ~20 new endpoint paths: instances, assignments, signoffs, public entry,
|
||
clone, and team roles. Added tags: Workflow Instances, Workflow Assignments,
|
||
Workflow Signoffs, Public Workflows.
|
||
|
||
## v0.3.4 — Team Roles + Multi-party Validation
|
||
|
||
### Added
|
||
|
||
- **Custom team roles**: Removed `CHECK (role IN ('admin','member'))` constraint
|
||
from `team_members` (both Postgres and SQLite). Custom roles are stored in
|
||
`teams.settings["roles"]` as a JSON array. Builtins `admin` and `member` are
|
||
always present.
|
||
- **Team roles API**: `GET /api/v1/teams/:teamId/roles` returns configured roles.
|
||
`PUT /api/v1/teams/:teamId/roles` replaces the roles array (builtins enforced).
|
||
- **Role-based stage assignment**: `stage_config.required_role` restricts which
|
||
team members can claim an assignment. `CheckClaimRole` verifies the user's team
|
||
membership role before allowing claim; rolls back on mismatch (403).
|
||
- **Multi-party sign-off**: New `workflow_signoffs` table with
|
||
`UNIQUE(instance_id, stage, user_id)` preventing double-signing.
|
||
`StageConfig.validation` supports `required_approvals`, `required_role`, and
|
||
`reject_action` (cancel or reroute to named stage).
|
||
- **Validation gate**: `advanceInternal` blocks stage advancement until the
|
||
required number of approvals is met. Rejections trigger cancel or reroute
|
||
based on `reject_action`.
|
||
- **`Engine.SubmitSignoff`**: Records approval/rejection, enforces signoff role
|
||
if configured, emits `workflow.signoff` event.
|
||
- **Signoff HTTP API**: `POST /api/v1/instances/:iid/signoffs` (submit),
|
||
`GET /api/v1/instances/:iid/signoffs` (list current stage signoffs).
|
||
Team-scoped mirrors at `/api/v1/teams/:teamId/instances/:iid/signoffs`.
|
||
- **New events**: `workflow.signoff`, `workflow.rejected`.
|
||
- **Frontend — Members page**: Dynamic role selects populated from team roles API.
|
||
"Manage Roles" panel for adding/removing custom roles inline.
|
||
- **Frontend — Stage editor**: "Required Role (claim)" dropdown and "Multi-party
|
||
Validation" section (required approvals, signoff role, on reject action) appear
|
||
when a team is selected for the stage.
|
||
- **Frontend — Monitor tab**: Signoff panel with approve/reject buttons, comment
|
||
field, and approval progress display.
|
||
- **3 new store tests**: CreateSignoff (with UNIQUE constraint check),
|
||
ListSignoffs (ordering), CountSignoffs (decision filter). 20 total, all passing.
|
||
- **Design doc**: `docs/DESIGN-EXTENSION-LIFECYCLE.md` — permanent vs PoC
|
||
packages, graduation criteria, explicit install model.
|
||
- **Design doc**: `docs/DESIGN-TRIGGER-COMPOSITION.md` — triggers/schedules can
|
||
start workflows, workflows emit events, no circular invocation.
|
||
|
||
### Changed
|
||
|
||
- `addMemberRequest` and `updateMemberRequest` binding relaxed from
|
||
`oneof=admin member` to `required,min=1,max=50` with application-level
|
||
validation against the team's configured roles.
|
||
- `TruncateAll` test helper updated to include `workflow_signoffs`.
|
||
|
||
---
|
||
|
||
## v0.3.3 — Public Entry + Background Jobs
|
||
|
||
### Added
|
||
|
||
- **Public workflow entry**: Unauthenticated routes for anonymous workflow
|
||
participation. `POST /api/v1/public/workflows/:id/start` creates an instance
|
||
with `started_by = "public:<uuid>"` and returns an entry token.
|
||
`GET .../resume/:token` and `POST .../advance/:token` allow continuation.
|
||
Only stages with `audience = "public"` can be advanced anonymously.
|
||
- **SLA scanner**: Background goroutine (5-minute interval) checks active
|
||
instances against per-stage `sla_seconds`. Fires `workflow.sla_breach` event
|
||
on first breach, marks `sla_breached: true` in instance metadata (idempotent).
|
||
- **Staleness sweep**: New `staleness_timeout_hours` column on `workflows`.
|
||
Scanner marks instances as `stale` when `updated_at` exceeds the threshold,
|
||
cancels open assignments, fires `workflow.stale` event.
|
||
- **New store methods**: `ListActiveInstances()`, `MarkInstanceStale()`.
|
||
- **New events**: `workflow.sla_breach`, `workflow.stale`.
|
||
- **3 new store tests**: MarkStale, ListActive, StalenessTimeoutHours round-trip.
|
||
|
||
---
|
||
|
||
## v0.3.2 — Workflow Engine
|
||
|
||
### Added
|
||
|
||
- **Stage execution engine**: `server/workflow/engine.go` — `Start`, `Advance`
|
||
(with `branch_rules` conditional routing), and `Cancel` operations. Merges
|
||
`stage_data` across stages, creates assignments, emits lifecycle events.
|
||
- **Automated stage handler**: `server/workflow/automated.go` — fires Starlark
|
||
hook, auto-advances on success, cycle guard (max 10 consecutive automated
|
||
stages).
|
||
- **Instance HTTP API**: Start, GetInstance, Advance, Cancel, ListInstances.
|
||
Team-scoped mirrors under `/api/v1/teams/:teamId/workflows/`.
|
||
- **Assignment HTTP API**: Claim, Unclaim, Complete, Cancel, ListByTeam,
|
||
ListMine. Claimer identity verification on unclaim/complete.
|
||
- **Starlark module expansion**: `workflow.get_instance()`,
|
||
`workflow.list_instances()` (read-only).
|
||
- **14 store tests** covering all 15 v0.3.1 methods (instance + assignment
|
||
CRUD). All passing.
|
||
|
||
---
|
||
|
||
## v0.3.1 — Instance Assignment
|
||
|
||
### Added
|
||
|
||
- **`workflow_instances` table**: Tracks workflow execution state —
|
||
`workflow_version`, `current_stage`, `stage_data`, `status`, `entry_token`.
|
||
- **`workflow_assignments` table**: Per-stage queue for instance assignments —
|
||
`instance_id`, `stage`, `team_id`, `assigned_to`, `status`, `review_data`.
|
||
Optimistic claim locking via status transition.
|
||
- **15 new store methods**: Full CRUD for instances (Create, Get, GetByToken,
|
||
Update, List, AdvanceStage, Complete, Cancel) and assignments (Create, Claim,
|
||
Unclaim, Complete, Cancel, ListByTeam, ListByInstance, ListByUser).
|
||
- **New events**: `workflow.started`, `workflow.cancelled`, `workflow.error`.
|
||
|
||
---
|
||
|
||
## v0.3.0 — Workflow Schema Redesign
|
||
|
||
### Changed
|
||
|
||
- **`workflow_stages` schema modernized**: Dropped chat-era columns (`persona_id`,
|
||
`history_mode`). Renamed `transition_rules` → `stage_config`. Updated
|
||
`stage_mode` CHECK from `(form_only, form_chat, review, custom)` to
|
||
`(form, review, delegated, automated)`.
|
||
- **New stage fields**: Added `audience` (team | public | system), `stage_type`
|
||
(simple | dynamic | automated), `starlark_hook` (package_id:entry_point),
|
||
and `branch_rules` (JSONB array of routing conditions).
|
||
- **Routing engine**: `ResolveNextStage` now reads `branch_rules` (flat array)
|
||
instead of `transition_rules.conditions` (nested object). Cleaner separation
|
||
between routing rules and stage config.
|
||
- **Hook handler**: `FireOnAdvanceHook` reads from `stage_config` instead of
|
||
`transition_rules`. Renamed `channelID` parameter to `instanceID`.
|
||
- **Stage CRUD validation**: New fields validated on create/update. `delegated`
|
||
mode requires `surface_pkg_id`. `dynamic`/`automated` stage_type requires
|
||
`starlark_hook`.
|
||
- **Package export/import**: Updated to use new field names. Workflow packages
|
||
now include `audience`, `stage_type`, `starlark_hook`, `branch_rules`,
|
||
`stage_config`.
|
||
- **Starlark workflow module**: Stage dicts now include `audience` and
|
||
`stage_type` keys. Removed `history_mode` and `persona_id`.
|
||
- **Frontend stage editor**: Both admin and team-admin surfaces updated with
|
||
new mode values, audience selector, stage type selector, and conditional
|
||
Starlark hook input. Fixed pre-existing bug in admin workflows.js where
|
||
`STAGE_MODES` still contained `chat_only`.
|
||
|
||
### Removed
|
||
|
||
- `persona_id` column from `workflow_stages` (personas are extension concerns)
|
||
- `history_mode` column from `workflow_stages` (chat-era context management)
|
||
- `StageModeCustom`, `StageModeFormOnly`, `StageModeFormChat` Go constants
|
||
|
||
---
|
||
|
||
## v0.2.9 — Builtin Extension Retirement
|
||
|
||
### Changed
|
||
|
||
- **6 builtin extensions → standard packages**: csv-table, diff-viewer,
|
||
js-sandbox, katex-renderer, mermaid-renderer, and regex-tester moved from
|
||
`extensions/builtin/` to `packages/` with standard `js/` layout. Each
|
||
manifest now declares `"requires": ["chat"]` and `"type": "extension"`.
|
||
Built via `build.sh` like all other packages — no auto-install.
|
||
|
||
### Removed
|
||
|
||
- **`SeedBuiltinPackages` seeder**: Deleted `seed_packages.go` (function,
|
||
`builtinManifest` struct, `buildFullManifest` helper) and the startup call
|
||
in `main.go`. Extensions are no longer auto-seeded into the DB.
|
||
- **`extensions/builtin/` directory**: Removed from repo and Dockerfile COPY.
|
||
- **Seed tests**: Removed 8 `TestSeed_*` functions and `makeSeedDir` helper
|
||
from `extension_test.go` (~220 lines).
|
||
- **Stale comments**: Removed `SeedBuiltinPackages` references from
|
||
`package_iface.go` and `trigger_sync.go`.
|
||
|
||
---
|
||
|
||
## v0.2.8 — Team Admin Settings Audit (Pass 1)
|
||
|
||
### Removed
|
||
|
||
- **Dead `HasPrivateProviderRequirement`**: Store method checked team settings
|
||
for `require_private_providers` (BYOK vestige). Removed from interface,
|
||
PostgreSQL, and SQLite implementations. No callers existed.
|
||
- **Dead `UserRole` field on `TeamMember`**: Joined `users.role` column
|
||
(deprecated in v0.2.0 RBAC migration). Removed from model, `ListMembers`
|
||
and `GetMember` queries in both stores. Frontend never consumed it.
|
||
- **Dead `allow_team_providers` policy**: Removed from `PolicyDefaults`, both
|
||
test seed data blocks, and ICD test assertions. No handler or UI read it.
|
||
- **Dead personas in workflow stage UI**: Removed `sw.api.teams.personas()`
|
||
call (endpoint doesn't exist), personas state, persona dropdown in
|
||
StageForm, and persona badge in stage list.
|
||
- **Dead `history_mode` in stage UI**: Removed history mode selector and state
|
||
from StageForm. Backend column retained for v0.3.x schema migration.
|
||
- **Dead ICD tests**: Removed assertions for `/teams/:teamId/personas`,
|
||
`/teams/:teamId/providers`, and `/teams/:teamId/models` — endpoints no
|
||
longer exist in the kernel.
|
||
- **Stale `chat_only` stage mode**: Frontend `STAGE_MODES` updated from
|
||
`['chat_only', 'form_only', 'form_chat', 'review']` to
|
||
`['form_only', 'form_chat', 'review', 'custom']` matching the backend
|
||
CHECK constraint.
|
||
- **Stale comments**: Removed references to deleted `team_providers.go`,
|
||
`personas.go`, and `apiconfigs.go` files.
|
||
|
||
---
|
||
|
||
## v0.2.7 — User Settings Audit
|
||
|
||
### Changed
|
||
|
||
- **localStorage namespace**: Renamed `cs-appearance` key to `sb-appearance`
|
||
across appearance settings, base template, and workflow template. One-time
|
||
migration preserves existing user preferences.
|
||
- **Policy-gating tests**: Replaced stale `allow_user_byok` / `allow_user_personas`
|
||
assertions with `allow_registration` check (the only policy still in admin UI).
|
||
|
||
### Removed
|
||
|
||
- **Dead BYOK nav section** in user settings: empty `BYOK_ITEMS` array,
|
||
`byokEnabled` state, "BYOK Enabled" footer badge, and the nav group
|
||
that rendered an empty section.
|
||
- **Dead personas gate**: `personasEnabled` state and `.filter()` on
|
||
NAV_ITEMS for a `gate` property no items have. `auth.permissions.changed`
|
||
listener removed (existed solely for BYOK + personas state).
|
||
- **Dead Message Font Size**: Slider, `msgFont` state, and `--msg-font`
|
||
CSS variable application from appearance section and base template.
|
||
- **Dead policy defaults**: `allow_user_byok` and `allow_user_personas`
|
||
removed from `PolicyDefaults`, profile bootstrap, permissions handler,
|
||
and `PublicSettings`. Test seed data cleaned.
|
||
|
||
---
|
||
|
||
## v0.2.6 — Admin Settings Audit
|
||
|
||
### Changed
|
||
|
||
- **Roadmap reorder**: Extension Lifecycle moved from v0.2.6 to v0.3.x
|
||
(Workflows series). Settings audit milestones renumbered: v0.2.7→v0.2.6,
|
||
v0.2.8→v0.2.7, v0.2.9→v0.2.8. Added v0.2.9 for builtin extension
|
||
retirement (chat-centric extensions dormant until chat surface ships).
|
||
|
||
### Removed
|
||
|
||
- **Dead admin section categories** in `sectionCategory()`: AI (providers,
|
||
models, personas, roles, knowledgeBases, memory), routing (health, routing,
|
||
capabilities), and channels. Default category changed from `ai` to `system`.
|
||
- **Dead PublicSettings fields**: `system_prompt`/`has_admin_prompt`,
|
||
`retention_ttl_days`, `paste_to_file_chars`, `allow_user_personas` policy
|
||
— all chat-era with no frontend consumers.
|
||
- **Dead PolicyDefaults**: `allow_raw_model_access`, `default_model`.
|
||
- **Dead policy lookups**: `allow_raw_model_access` and `kb_direct_access`
|
||
from profile bootstrap and permissions handlers.
|
||
- **Dead test seed data**: `model_roles` global setting, `allow_raw_model_access`
|
||
policy from test helper.
|
||
- **Dead CORE_IDS entry**: Removed `chat` from packages admin page.
|
||
|
||
---
|
||
|
||
## [Unreleased] — v0.2.5
|
||
|
||
### Added
|
||
|
||
- **Welcome surface**: New core surface shown as fallback. Detects whether
|
||
extensions exist (shows "Set Default Surface") vs truly empty install
|
||
(shows "Go to Packages"). Topbar + UserMenu included.
|
||
- **User default surface**: Users can set a personal landing page in
|
||
Settings > General, overriding the admin-configured global default.
|
||
- **UserMenu in admin topbar**: Replaced Back button with UserMenu,
|
||
providing consistent surface navigation and eliminating infinite loops.
|
||
- **Package manifest icons**: Added emoji icons to hello-dashboard,
|
||
icd-test-runner, sdk-test-runner, and team-activity-log manifests.
|
||
- **PoC documentation**: README.md for tasks and schedules packages
|
||
documenting Proof of Concept status and graduation criteria.
|
||
|
||
### Changed
|
||
|
||
- **Default surface resolution**: Priority chain is now
|
||
user preference → global config → first extension → `/welcome`.
|
||
User preference read from JWT cookie on unauthenticated `/` route.
|
||
Correctly resolves `type: "full"` extension packages (not just `type: "surface"`).
|
||
- **User settings General section**: Replaced dead chat fields (Default
|
||
Model, System Prompt, Max Tokens, Temperature, Show Thinking) with
|
||
a Default Surface dropdown.
|
||
- **Admin settings**: Removed dead chat sections (System Prompt, Default
|
||
Model, Policies, Web Search, Auto-Compaction, Memory Extraction).
|
||
- **Roadmap restructured**: Workflows → v0.3.x (includes team roles),
|
||
Notes → v0.4.0, MVP → v0.5.0. Added settings audit milestones
|
||
(v0.2.7 admin, v0.2.8 user, v0.2.9 team-admin pass 1).
|
||
- Updated bus doc examples and test labels from `chat.*` to `workflow.*`.
|
||
- Renamed `channel-` prefixed test paths to `test-` in storage tests.
|
||
- Updated doc comments throughout to reflect current surface architecture.
|
||
|
||
### Removed
|
||
|
||
- **~500 lines of dead CSS**: Orphaned classes for removed legacy UI
|
||
components (chat, channel, project, notes, editor-chat, sidebar,
|
||
router-picker) from `layout.css` and `surfaces.css`.
|
||
- **Dead Go types**: `Grant` struct (persona-era), `CompositeModelKey` func,
|
||
comment-only stubs for NoteGraph, ProjectChannel, etc.
|
||
- **Dead event code**: `chat.typing.*` / `channel.typing.*` condition in WS
|
||
subscriber, empty Chat/Channel event route table sections.
|
||
- **Dead test helpers**: `seed_helpers.go` (SeedTestMessage, SeedTestMessages,
|
||
SeedTestCursor — all callerless).
|
||
- **Stale template refs**: CSS link tags for non-existent `sw-chat-pane.css`,
|
||
`sw-notes-pane.css`, `chat.css`. Orphaned `chat-pane.html` component.
|
||
- **Vestigial guards**: `"chat"` surface checks in `IsSurfaceEnabled()` and
|
||
`DisablePackage()` (chat is no longer a surface).
|
||
- **Dead settings UI**: Chat defaults from user settings, System Prompt /
|
||
Default Model / Policies / Web Search / Compaction / Memory from admin.
|
||
- Unused `fmt.Sprintf` references in team stores.
|
||
|
||
---
|
||
|
||
## [Unreleased] — v0.2.4
|
||
|
||
### Added
|
||
|
||
- **`sw.shell.Topbar`**: Standard navigation bar component for surfaces.
|
||
Composes title + extension slot + NotificationBell + UserMenu into a
|
||
consistent 44px bar. Surfaces use `<${sw.shell.Topbar} title="...">` with
|
||
children rendered in the extension slot. Graceful fallback if unavailable.
|
||
- **Schedules surface** (`packages/schedules/`): New package wrapping the
|
||
kernel `/api/v1/schedules` API. Table view with cron badge + human-readable
|
||
preview, next fire time, enable/disable toggle, manual run, and execution
|
||
logs panel. Create/edit dialog with live cron-to-english preview.
|
||
- **Manifest `icon` field**: Packages can declare an emoji icon in
|
||
`manifest.json` (`"icon": "⏰"`). Served via the surfaces API `icon` field.
|
||
Rendered in the UserMenu flyout next to each surface name.
|
||
|
||
### Changed
|
||
|
||
- **UserMenu**: Surface list now driven entirely by the `/api/v1/surfaces`
|
||
API. Removed hardcoded surface links; menu is now fully dynamic.
|
||
Core surfaces (Admin, Settings, Team Admin, Workflow) filtered from the
|
||
API list and handled as dedicated menu items with RBAC gating.
|
||
- **Tasks surface**: Replaced custom `.tasks-header` with `sw.shell.Topbar`.
|
||
View tabs and create button rendered in the extension slot.
|
||
|
||
### Fixed
|
||
|
||
- **`sw.isAdmin` RBAC regression**: `isAdmin()` in `can.js` was checking the
|
||
deprecated `user.role === 'admin'` field instead of the v0.2.0 RBAC grant
|
||
`surface.admin.access`. Admin menu item and admin-gated features now appear
|
||
correctly for users in the Admins group.
|
||
|
||
---
|
||
|
||
## [Unreleased] — v0.2.2
|
||
|
||
### Added
|
||
|
||
- **Event bus subscriptions**: Extensions declare event triggers in manifest
|
||
(`"triggers": [{"type": "event", "pattern": "workflow.completed", ...}]`).
|
||
Wired via `bus.Subscribe()` on startup. Handlers fire asynchronously.
|
||
- **Webhook triggers**: Inbound HTTP at `/api/v1/hooks/:package_id/:slug`.
|
||
HMAC-SHA256 verification via `X-Armature-Signature` header. Synchronous
|
||
Starlark handler can return custom HTTP status and body.
|
||
- **Scheduled tasks**: User-created cron-scheduled Starlark scripts with
|
||
restricted sandbox (no raw HTTP, no DB table creation, connections-only
|
||
outbound). Runs as creator identity with RBAC scoping. Admin-created tasks
|
||
can opt into system context. Creator deactivation auto-pauses schedule.
|
||
- **Schedule templates**: Extensions ship pre-built schedule templates in
|
||
manifest (`schedule_templates` array) with configurable params and default
|
||
cron expressions.
|
||
- `triggers.register` extension permission — required for event/webhook triggers
|
||
- `triggers` table — extension-declared event and webhook trigger definitions
|
||
- `scheduled_tasks` table — user-created cron tasks with script, template,
|
||
and identity fields
|
||
- `trigger_logs` table — unified execution audit log for both tiers
|
||
- `TriggerStore` + `ScheduledTaskStore` interfaces (postgres + sqlite)
|
||
- Trigger engine (`server/triggers/`) — orchestrates event subscriptions,
|
||
webhook resolution, and cron scheduling via `robfig/cron/v3`
|
||
- `SyncManifestTriggers()` — declarative sync of event/webhook triggers from
|
||
manifest. Hooked into seed, admin install, and package install flows.
|
||
- Admin trigger API: `GET/PUT/DELETE /admin/triggers`, `/admin/triggers/:id/logs`,
|
||
`/admin/packages/:id/triggers`
|
||
- Admin schedule API: `GET /admin/schedules`, enable/disable/delete
|
||
- User schedule API: full CRUD at `/api/v1/schedules`, manual run, execution logs
|
||
- `trigger.fired` and `trigger.error` event bus labels (DirLocal) for observability
|
||
- OpenAPI spec: Trigger, ScheduledTask, TriggerLog schemas + all new endpoints
|
||
|
||
---
|
||
|
||
## [v0.2.1] — 2026-03-26
|
||
|
||
### Added
|
||
|
||
- **Default surface routing**: `/` redirects to configurable default surface.
|
||
Fallback chain: configured default → first enabled extension surface → `/admin`.
|
||
First installed extension surface auto-becomes default. Admin can change via
|
||
Settings > Default Surface dropdown.
|
||
- `default_surface` global config key (JSON `{"id": "slug"}`)
|
||
- Admin settings UI: Default Surface dropdown (extension surfaces only)
|
||
- **ICD (API contract)**: Full OpenAPI 3.0.3 spec covering all 160 kernel
|
||
endpoints. 22 tag groups (System, Auth, Profile, Workflows, Packages,
|
||
Connections, Teams, Groups, Extensions, and Admin subsections). Reusable
|
||
component schemas for User, Team, Group, Workflow, Package, Extension, etc.
|
||
Served at `/api/docs` (Swagger UI) and `/api/docs/openapi.yaml`.
|
||
|
||
### Changed
|
||
|
||
- `disabledRedirect()` now redirects to `/admin` instead of `/` to prevent
|
||
redirect loops when the default surface is disabled
|
||
- Disabled extension surfaces (`/s/:slug`) redirect to `/admin` instead of `/`
|
||
|
||
### Fixed
|
||
|
||
- **Gin route param conflict** causing backend startup hang: team-scoped
|
||
package settings routes used `:pkgId` while sibling routes used `:id`.
|
||
Gin's radix tree entered an infinite loop on the conflicting param names.
|
||
Unified to `:id` across all `/teams/:teamId/packages/` routes.
|
||
- Docker entrypoint: increased health check timeout (10s → 60s), added stale
|
||
process cleanup and crash detection to prevent zombie backends on restart
|
||
|
||
---
|
||
|
||
## [v0.2.0] — 2026-03-26
|
||
|
||
### Added
|
||
|
||
- **Full RBAC**: all authorization flows through group membership and permission
|
||
grants. No magic roles, no implicit group membership, no special-casing.
|
||
- `surface.admin.access` permission — any group can grant admin panel access
|
||
- Admins system group seeded with all platform permissions
|
||
- Everyone system group — all users explicitly added on creation
|
||
- `EnsureEveryoneGroup()`, `AddToAdminsGroup()`, `RemoveFromAdminsGroup()` helpers
|
||
- `SeedAdminsGroupMember()`, `SeedEveryoneGroupMember()` test helpers
|
||
- System groups re-seeded after `TruncateAll` in test helper
|
||
- OIDC `isIdPAdmin()` — maps IdP role claims to Admins group membership
|
||
- **Settings cascade**: three-tier resolution (global → team → user) with
|
||
`user_overridable` flag per manifest setting key. Admins can lock settings
|
||
that team admins and users cannot override.
|
||
- `package_team_settings` table for team-scoped package setting overrides
|
||
- Team admin API: `GET/PUT/DELETE /api/v1/teams/:teamId/packages/:pkgId/settings`
|
||
- `RunContext.TeamID` for team-aware Starlark settings resolution
|
||
- `store.ResolveSettings()` / `store.FilterOverridableKeys()` pure functions
|
||
- `store.ParseSettingsSchema()` extracts `user_overridable` from manifests
|
||
|
||
### Changed
|
||
|
||
- `RequireAdmin()` / `RequireAdminPage()` check `surface.admin.access` grant
|
||
- `RequirePermission()` no longer bypasses for admin role
|
||
- `ResolvePermissions()` unions explicit group memberships only (no implicit Everyone)
|
||
- All user creation paths (builtin, OIDC, mTLS, admin, bootstrap, seed) add to
|
||
Everyone group. Admin users added to Admins group.
|
||
- JWT claims no longer include `role` field
|
||
- Login response no longer includes `role` in user object
|
||
- Profile endpoint no longer returns `role`
|
||
- Profile bootstrap resolves permissions from groups (no admin shortcut)
|
||
- Middleware auth cache tracks `isActive` only (no role)
|
||
- Admin create user accepts `is_admin` bool (not role string)
|
||
- Admin update role endpoint accepts `is_admin` bool, manages Admins group directly
|
||
- Demotion/deletion safeguards check Admins group member count
|
||
- Notifications `RoleFallbackHandler` queries Admins group members
|
||
- OIDC syncs Admins group on login (no role column writes)
|
||
- Kernel permissions: 6 → 7 (added `surface.admin.access`)
|
||
- Admin users UI: role dropdown removed, admin managed through groups
|
||
- Starlark `settings.get()` uses cascade resolver instead of naive merge
|
||
- User settings save (`POST /extensions/:id/settings`) strips non-overridable keys
|
||
|
||
### Removed
|
||
|
||
- **`users.role` column** — dropped from schema, model, JWT, all handlers
|
||
- `UserRoleAdmin`, `UserRoleUser` constants
|
||
- `CountByRole()` store method
|
||
- `DefaultRole` config for OIDC and mTLS providers
|
||
- `SyncAdminsGroupMembership()` (replaced by `AddToAdminsGroup`/`RemoveFromAdminsGroup`)
|
||
- OIDC `resolveRole()` (replaced by `isIdPAdmin()`)
|
||
- **Token budgets** from groups: columns, `ResolveTokenBudget()`, all store/handler/UI
|
||
- **Allowed models** from groups: column, `ResolveModelAllowlist()`, UI
|
||
- Admin groups UI: Token Budgets section, Allowed Models section
|
||
|
||
### Migration notes
|
||
|
||
- 001_core.sql (both dialects): removed `role` column from users table
|
||
- 002_teams.sql (both dialects): added Admins group seed, removed
|
||
`token_budget_daily`, `token_budget_monthly`, `allowed_models` columns
|
||
- No new migration files — edited in place per pre-MVP policy
|
||
|
||
---
|
||
|
||
## [v0.1.0] — 2026-03-26
|
||
|
||
Initial release. Pure extension platform — kernel provides auth, RBAC,
|
||
storage, and the Starlark sandbox. Everything else is a package.
|
||
|
||
### Removed
|
||
|
||
- **AI/Chat system**: providers, model catalog, routing policies, personas,
|
||
channels, messages, completion streaming, tool loop, compaction, memory,
|
||
knowledge bases, notes, workspaces, projects, folders, files, export/import
|
||
- **Task scheduler**: entire scheduler package, task store, task handlers.
|
||
Tasks will be rebuilt as a Starlark extension with three trigger primitives
|
||
(time, webhook, event)
|
||
- **Session system**: channel-based anonymous sessions. Workflow instances
|
||
will get new storage in v0.2.0
|
||
- **Health accumulator**: provider health windows, tool health tracking.
|
||
Replaced with kernel-only Prune (ws_tickets, rate_limit_counters, presence)
|
||
- **15 Go packages**: tools, compaction, extraction, roles, mentions,
|
||
notelinks, export, memory, knowledge, providers, routing, capabilities,
|
||
filters, retention, workspace
|
||
- **29 handler files**, 6 test files, ~44K lines total
|
||
|
||
### Fixed
|
||
|
||
- CI deploy: k8s resource quantity vars (`BE_MEMORY_REQUEST` → `MEMORY_REQUEST`)
|
||
aligned with CI workflow outputs — `envsubst` was producing empty strings
|
||
- CI deploy: image var (`BE_IMAGE` → `IMAGE`) — caused `InvalidImageName` in pods
|
||
- CI rollout: deployment name (`switchboard` → `armature-be`) — rollout
|
||
verification was looking for wrong deployment name
|
||
- Nginx BASE_PATH: regex cache-header locations intercepted static asset
|
||
requests before alias could strip the sub-path prefix — moved inside alias block
|
||
- Post-login blank page: dead Go template references (`surface-chat`,
|
||
`surface-notes`, `surface-projects`) caused html/template to silently
|
||
produce Content-Length: 0 responses
|
||
- Login branding: "Chat Armature" → "Armature", updated tagline
|
||
and feature pills to reflect platform pivot
|
||
|
||
### Changed
|
||
|
||
- Go module: `armature`
|
||
- VERSION: `0.1.0`
|
||
- Default DB name: `armature`
|
||
- Fresh migrations: 9 files × 2 dialects (postgres + sqlite), 27 tables
|
||
- Store interfaces: 40 → 20 (13 in interfaces.go + 7 in separate iface files)
|
||
- Stage modes: `chat_only` removed, `custom` added
|
||
- Task output modes: `channel|note|webhook` → `notification|webhook|log`
|
||
- Kernel permissions: 16 → 6 (`extension.use`, `extension.install`,
|
||
`workflow.create`, `workflow.submit`, `admin.view`, `token.unlimited`)
|
||
- Everyone group seed: `["extension.use","workflow.submit"]`
|
||
- Global settings seed: site name "Armature"
|
||
- Config: removed 7 dropped fields (SessionExpiryDays, WorkflowStaleHours,
|
||
ProviderAutoDisableThreshold, ExtractionConcurrency, etc.)
|
||
- Health stores rewritten: kernel-only Prune for stale tickets, counters, presence
|
||
- Maintenance goroutine replaces scheduler for background cleanup
|
||
|
||
### Retained
|
||
|
||
- Identity & auth (builtin, mTLS, OIDC)
|
||
- Teams, groups, permissions
|
||
- Package system (surfaces, extensions, libraries, workflows)
|
||
- Starlark sandbox with capability-gated modules
|
||
- Extension connections & dependencies
|
||
- Workflow definitions, stages, versions
|
||
- Notifications & preferences
|
||
- Audit log
|
||
- Object storage (PVC, S3)
|
||
- WebSocket hub & presence
|
||
- Multi-replica HA (ws_tickets, rate_limit_counters)
|
||
- Frontend shell (preact+htm, SDK, vendor libs)
|