This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/CHANGELOG.md
Jeffrey Smith c9b9e68c18
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-frontend (push) Successful in 5s
CI/CD / test-go-pg (push) Successful in 2m42s
CI/CD / test-sqlite (push) Successful in 3m1s
CI/CD / build-and-deploy (push) Successful in 1m8s
Feat v0.6.14 visual polish (#49)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
2026-04-01 13:25:04 +00:00

86 KiB
Raw Blame History

Changelog

All notable changes to Armature are documented here.

v0.6.14 — Visual Polish

Systematic cleanup of stale values, self-hosted fonts, and rendering fixes. Final visual pass before the v0.6.15 usability survey gate.

Fixed

  • v0.6.13 spacing regressions — added half-step tokens (--sp-1h 6px, --sp-2h 10px) and restored correct padding on .sw-btn--sm, .sw-input, .sw-menu__item, .sw-dropdown__option, .sw-tabs__tab.
  • Theme settings toggle — showed resolved theme ("Dark") instead of stored mode ("System"). Changed appearance.js to read sw.theme.mode.
  • Notes surface scrollbar — added overflow: hidden to .surface-inner in base.html, preventing spurious scrollbar at any scale.
  • Chat input clipped at high scale — same overflow: hidden fix prevents zoomed content from overflowing the surface container.
  • User menu drift at scale > 100%menu.js now divides getBoundingClientRect() coords by the CSS zoom factor, fixing position for position: fixed menus inside a zoomed ancestor.
  • Undefined variables--text-secondary (login), --text-muted (user picker), --text-1 (settings toggle) replaced with correct tokens.

Changed

  • Stale fallback colors purged — removed ~65 hex/rgba fallback values from var() calls across 9 kernel CSS files and 3 extension packages. Old gold theme color #b38a4e fully eliminated (9 instances).
  • Self-hosted fonts — bundled DM Sans and JetBrains Mono woff2 files in src/fonts/. Replaced Google Fonts @import and login.html <link> with local @font-face declarations. Zero external font dependencies.
  • Consistent border-radius — added --radius-sm: 4px token. Migrated ~60 hardcoded border-radius values across all kernel CSS and 12 extension packages to three tokens: --radius-sm (4px), --radius (8px), --radius-lg (12px).

Updated

  • docs/EXTENSION-CSS.md — added --sp-1h, --sp-2h half-step tokens and --radius-sm to the public CSS contract.

v0.6.13 — Responsive & Spacing

Spacing token scale and tablet breakpoint. All kernel CSS and extension packages migrated from hardcoded values to design tokens.

Added

  • Spacing tokens (--sp-1 through --sp-12) — 4px-grid scale in variables.css. Nine stops: 4, 8, 12, 16, 20, 24, 32, 40, 48px. Numeric naming (--sp-N), rem-based for zoom/font-size respect.
  • Tablet breakpoint (max-width: 1024px) — new responsive tier between mobile (768px) and desktop. Secondary workspace pane narrows to 360px, admin sidebar to 120px, settings/admin/editor navs shrink.
  • Breakpoint documentation in EXTENSION-CSS.md — Mobile (768px), Tablet (1024px), Desktop (default).
  • Spacing guidelines in EXTENSION-CSS.md — token table with computed pixel values and usage examples.

Changed

  • 8 kernel CSS files migrated to spacing tokens — sw-primitives.css, modals.css, surfaces.css, layout.css, sw-shell.css, primitives.css, user-menu.css, sw-login.css. Hardcoded padding, margin, and gap values replaced with var(--sp-N).
  • 12 extension packages migrated — chat, dashboard, editor, git-board, hello-dashboard, icd-test-runner, notes, schedules, sdk-test-runner, tasks, team-activity-log, workflow-demo.
  • Login hero breakpoint normalized from 900px to 1024px (tablet).
  • Notes mobile breakpoint normalized from 700px to 768px (standard).

v0.6.12 — Extension CSS Isolation

Prefix enforcement prevents extension CSS from leaking into the kernel or sibling extensions. All 12 in-tree packages migrated to .ext-{slug}-* naming convention.

Added

  • data-ext attribute on extension mount container — enables scoped selectors like [data-ext="chat"] .ext-chat-app.
  • CSS linter (scripts/lint-package-css.sh) — validates that the first class selector in every extension CSS rule starts with .ext-{slug}. Exempts :root, @keyframes, @font-face, @media, kernel .sw-* classes, and CodeMirror .cm-* classes.
  • Kernel CSS contract (docs/EXTENSION-CSS.md) — documents stable public classes and CSS variables that extensions may reference. Everything else is internal kernel CSS.

Changed

  • 12 packages migrated — all class selectors renamed to .ext-{slug}-*: chat, dashboard, editor, git-board, hello-dashboard, icd-test-runner, notes, schedules, sdk-test-runner, tasks, team-activity-log, workflow-demo. CSS and JS files updated in lockstep.
  • icd-test-runner — ID selectors (#extension-mount) converted to class-based selectors with proper prefix.
  • editor cross-references — compound selectors referencing notes classes updated to new .ext-notes-* names.
  • chat kernel overrides.sw-dialog:has(...) override scoped under [data-ext="chat"] instead of global.

v0.6.11 — CSS Deduplication

One class per concept. The old primitives.css button, toast, popup-menu, dropdown, and tabs systems are retired. sw-primitives.css is the single source of truth for all Preact component styles.

Changed

  • Buttons: All 29 files migrated from .btn-primary / .btn-small / .btn-danger / .btn-ghost / .btn-md / .btn-sm to the BEM-style .sw-btn .sw-btn--{variant} .sw-btn--{size} system.
  • Toasts: Old .toast-container / .toast CSS deleted. SDK's sw.toast() API already used .sw-toast-* classes — no JS changes.
  • Popup menus: Old .popup-menu / .popup-menu-item CSS deleted (unused — .sw-menu is the active system).
  • Dropdown collision resolved: Old .sw-dropdown (styled <select>) deleted from primitives.css. The sw-primitives.css custom dropdown component (.sw-dropdown with BEM sub-elements) is authoritative.
  • Tabs collision resolved: Old .sw-tabs / .sw-tab-btn deleted from primitives.css. The sw-primitives.css scrollable tabs component (.sw-tabs__tab) is authoritative.
  • .settings-section collision resolved: Removed duplicate definition from modals.css. The surfaces.css card-style definition is authoritative; .settings-content .settings-section override resets card styling for flat settings layouts.

Added

  • .sw-btn--success variant in sw-primitives.css (green action button).
  • --bg-active CSS variable in both dark/light themes (variables.css).
  • scripts/audit-css-collisions.sh — finds duplicate class selectors across kernel CSS files and outputs a JSON collision report.

Fixed

  • packages/sdk-test-runner/css/main.css: Wrong variable names (--text3--text-3, --text2--text-2, --bg1/--bg2--bg-raised).
  • packages/icd-test-runner/css/main.css: Replaced inline button fallback styles with kernel .sw-btn system.

Removed

  • Old button classes: .btn-primary, .btn-small, .btn-danger, .btn-full, .btn-ghost, .btn-subtle, .btn-sm, .btn-md.
  • Old toast classes: .toast-container, .toast, .toast.error/warning/success.
  • Old popup menu classes: .popup-menu, .popup-menu-item, .popup-menu-*.
  • Old dropdown and tabs definitions from primitives.css that collided with sw-primitives.css.

v0.6.10 — Viewport Foundation

Single layout model. Every surface renders inside one containment chain: body → shell → surface. No dual systems. No transform hacks.

Changed

  • CSS zoom replaces transform: scale(): UI scale (80%175%) now uses CSS zoom on #surfaceInner instead of transform: scale(). zoom reflows layout correctly — getBoundingClientRect() returns accurate values, eliminating the scale-correction hack in menu.js. Supported in all evergreen browsers (Firefox 126+, June 2024).
  • Single layout root: <body> in base.html is the authoritative flex column layout. .sw-shell CSS demoted from viewport-level container (height: 100vh) to fill-parent (height: 100%). Safe-area insets moved from .sw-shell to <body>.
  • Banner single source of truth: Template banners measure their own height via inline <script> and set --banner-top-height / --banner-bottom-height CSS variables. Removed --banner-h: 28px fixed variable. ShellBanner Preact component's useEffect measurement removed (dead code — no surface imports AppShell).
  • sw-shell__banner position: Changed from position: fixed to position: static — template banners are in-flow elements.
  • sw-shell__body padding: Removed padding-top/bottom for banner offsets — unnecessary with in-flow banners.
  • Extension surfaces 100vh100%: chat-app, chat-loading, surface-dashboard now use height: 100% to inherit from the extension mount container (like Notes). Fixes overflow behind banners.
  • 100vh100dvh fallbacks: All viewport-height declarations use height: 100vh; height: 100dvh; pattern for correct behavior on mobile browsers. Affects: base.html, sw-login.css, workflow.html, workflow-landing.html, primitives.css, git-board/css/main.css.
  • sw.shell.getScale() deprecated: Returns 1 always — CSS zoom handles layout reflow without manual correction.

Deprecated

  • src/js/sw/shell/app-shell.js, app.js, surface-viewport.js — no surface imports these. Layout root is <body> in base.html.

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 5m60m) and session.refresh_token_ttl (default 7d, clamp 1h90d) 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.

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.9v0.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

  • MTLSProviderMTLSProxyProvider: Renamed for clarity. Config type MTLSConfigMTLSProxyConfig. Constructor NewMTLSProviderNewMTLSProxyProvider. File mtls.gomtls_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.

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


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.1v0.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.goStart, 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_rulesstage_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_REQUESTMEMORY_REQUEST) aligned with CI workflow outputs — envsubst was producing empty strings
  • CI deploy: image var (BE_IMAGEIMAGE) — caused InvalidImageName in pods
  • CI rollout: deployment name (switchboardarmature-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|webhooknotification|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)