chore: strip remaining pre-fork version references
Second pass — removes chat-switchboard version numbers from config field docs, section headers, file headers, and test comments. These were pre-fork version tags that don't apply to switchboard-core. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,18 +26,18 @@ type Config struct {
|
|||||||
// Seed users (dev/test only) — CSV: "user:pass:role,user2:pass2:role2"
|
// Seed users (dev/test only) — CSV: "user:pass:role,user2:pass2:role2"
|
||||||
SeedUsers string
|
SeedUsers string
|
||||||
|
|
||||||
// API key encryption (required for v0.9.4+)
|
// API key encryption (required+)
|
||||||
// Used to derive AES-256 key for global/team provider API keys.
|
// Used to derive AES-256 key for global/team provider API keys.
|
||||||
// Personal keys use per-user UEK (derived from password).
|
// Personal keys use per-user UEK (derived from password).
|
||||||
EncryptionKey string
|
EncryptionKey string
|
||||||
|
|
||||||
// File storage (v0.12.0+)
|
// File storage
|
||||||
// STORAGE_BACKEND: "pvc" or "s3". Empty = auto-detect (pvc if path writable).
|
// STORAGE_BACKEND: "pvc" or "s3". Empty = auto-detect (pvc if path writable).
|
||||||
// STORAGE_PATH: mount point for PVC backend (default /data/storage).
|
// STORAGE_PATH: mount point for PVC backend (default /data/storage).
|
||||||
StorageBackend string
|
StorageBackend string
|
||||||
StoragePath string
|
StoragePath string
|
||||||
|
|
||||||
// S3-compatible storage (v0.12.0+)
|
// S3-compatible storage
|
||||||
// Works with AWS S3, MinIO, Ceph RGW, or any S3-compatible API.
|
// Works with AWS S3, MinIO, Ceph RGW, or any S3-compatible API.
|
||||||
S3Endpoint string // custom endpoint URL (required for MinIO/Ceph, optional for AWS)
|
S3Endpoint string // custom endpoint URL (required for MinIO/Ceph, optional for AWS)
|
||||||
S3Bucket string // bucket name (required when STORAGE_BACKEND=s3)
|
S3Bucket string // bucket name (required when STORAGE_BACKEND=s3)
|
||||||
@@ -47,16 +47,16 @@ type Config struct {
|
|||||||
S3Prefix string // optional key prefix within bucket (e.g. "switchboard/")
|
S3Prefix string // optional key prefix within bucket (e.g. "switchboard/")
|
||||||
S3ForcePathStyle bool // use path-style URLs (required for MinIO, most self-hosted)
|
S3ForcePathStyle bool // use path-style URLs (required for MinIO, most self-hosted)
|
||||||
|
|
||||||
// Structured logging (v0.33.0)
|
// Structured logging
|
||||||
// LOG_FORMAT: "text" (default, backward-compatible) or "json" (structured).
|
// LOG_FORMAT: "text" (default, backward-compatible) or "json" (structured).
|
||||||
// LOG_LEVEL: "debug", "info" (default), "warn", "error".
|
// LOG_LEVEL: "debug", "info" (default), "warn", "error".
|
||||||
LogFormat string
|
LogFormat string
|
||||||
LogLevel string
|
LogLevel string
|
||||||
|
|
||||||
// Auth mode (v0.24.0): "builtin" (default) | "mtls" | "oidc"
|
// Auth mode: "builtin" (default) | "mtls" | "oidc"
|
||||||
AuthMode string
|
AuthMode string
|
||||||
|
|
||||||
// mTLS (v0.24.1)
|
// mTLS
|
||||||
// Headers injected by TLS-terminating reverse proxy.
|
// Headers injected by TLS-terminating reverse proxy.
|
||||||
MTLSHeaderDN string // default "X-SSL-Client-DN"
|
MTLSHeaderDN string // default "X-SSL-Client-DN"
|
||||||
MTLSHeaderVerify string // default "X-SSL-Client-Verify"
|
MTLSHeaderVerify string // default "X-SSL-Client-Verify"
|
||||||
@@ -64,7 +64,7 @@ type Config struct {
|
|||||||
MTLSAutoActivate bool // auto-activate new users (default true)
|
MTLSAutoActivate bool // auto-activate new users (default true)
|
||||||
MTLSDefaultTeam string // team ID for auto-provisioned users (optional)
|
MTLSDefaultTeam string // team ID for auto-provisioned users (optional)
|
||||||
|
|
||||||
// Bundled packages (v0.3.8, curated v0.5.4)
|
// Bundled packages
|
||||||
// SKIP_BUNDLED_PACKAGES: set true to disable auto-install of bundled packages on first run.
|
// SKIP_BUNDLED_PACKAGES: set true to disable auto-install of bundled packages on first run.
|
||||||
// BUNDLED_PACKAGES_DIR: directory containing pre-built .pkg archives (default /app/bundled-packages).
|
// BUNDLED_PACKAGES_DIR: directory containing pre-built .pkg archives (default /app/bundled-packages).
|
||||||
// BUNDLED_PACKAGES: controls which bundled packages are auto-installed:
|
// BUNDLED_PACKAGES: controls which bundled packages are auto-installed:
|
||||||
@@ -75,7 +75,7 @@ type Config struct {
|
|||||||
BundledPackagesDir string
|
BundledPackagesDir string
|
||||||
BundledPackages string
|
BundledPackages string
|
||||||
|
|
||||||
// OIDC (v0.24.1)
|
// OIDC
|
||||||
OIDCIssuerURL string // e.g. "https://keycloak.corp/realms/switchboard"
|
OIDCIssuerURL string // e.g. "https://keycloak.corp/realms/switchboard"
|
||||||
OIDCExternalIssuerURL string // OIDC_EXTERNAL_ISSUER_URL
|
OIDCExternalIssuerURL string // OIDC_EXTERNAL_ISSUER_URL
|
||||||
OIDCClientID string
|
OIDCClientID string
|
||||||
@@ -87,7 +87,7 @@ type Config struct {
|
|||||||
OIDCGroupsClaim string // JWT claim for group sync (default "groups")
|
OIDCGroupsClaim string // JWT claim for group sync (default "groups")
|
||||||
OIDCAdminRole string // IdP role value that maps to admin (default "admin")
|
OIDCAdminRole string // IdP role value that maps to admin (default "admin")
|
||||||
|
|
||||||
// Cluster registry (v0.6.0)
|
// Cluster registry
|
||||||
// PG-backed node registry for horizontal scaling. No-op on SQLite.
|
// PG-backed node registry for horizontal scaling. No-op on SQLite.
|
||||||
// CLUSTER_NODE_ID: override for deterministic identity (default: hostname-PID).
|
// CLUSTER_NODE_ID: override for deterministic identity (default: hostname-PID).
|
||||||
// CLUSTER_HEARTBEAT_INTERVAL: tick frequency (default "10s").
|
// CLUSTER_HEARTBEAT_INTERVAL: tick frequency (default "10s").
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
// provider_configs.api_key_plain using the env-derived key, writing the
|
// provider_configs.api_key_plain using the env-derived key, writing the
|
||||||
// result to api_key_enc (BYTEA) + key_nonce. Clears api_key_plain after.
|
// result to api_key_enc (BYTEA) + key_nonce. Clears api_key_plain after.
|
||||||
//
|
//
|
||||||
// This runs once on upgrade from v0.9.3 → v0.9.4. Subsequent starts are
|
// This runs once on upgrade when ENCRYPTION_KEY is first set. Subsequent starts are
|
||||||
// a no-op (api_key_plain will be NULL for all rows).
|
// a no-op (api_key_plain will be NULL for all rows).
|
||||||
//
|
//
|
||||||
// Personal-scope keys are temporarily encrypted with the env key. They get
|
// Personal-scope keys are temporarily encrypted with the env key. They get
|
||||||
@@ -94,7 +94,7 @@ func EnforceEncryptionKey(db *sql.DB, encryptionKey string) error {
|
|||||||
|
|
||||||
// Check if any plaintext keys exist (pre-backfill)
|
// Check if any plaintext keys exist (pre-backfill)
|
||||||
var hasPlaintext bool
|
var hasPlaintext bool
|
||||||
// api_key_plain column may not exist on fresh v0.9.4+ installs
|
// api_key_plain column may not exist on fresh installs
|
||||||
var colExists bool
|
var colExists bool
|
||||||
db.QueryRow(`
|
db.QueryRow(`
|
||||||
SELECT EXISTS (
|
SELECT EXISTS (
|
||||||
@@ -121,7 +121,7 @@ func EnforceEncryptionKey(db *sql.DB, encryptionKey string) error {
|
|||||||
if hasPlaintext {
|
if hasPlaintext {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"ENCRYPTION_KEY is not set but plaintext API keys need encryption. " +
|
"ENCRYPTION_KEY is not set but plaintext API keys need encryption. " +
|
||||||
"Set ENCRYPTION_KEY before starting (required for v0.9.4+)",
|
"Set ENCRYPTION_KEY before starting (required+)",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
log.Println(" ⚠ ENCRYPTION_KEY not set — API key encryption disabled (OK for fresh install with no providers)")
|
log.Println(" ⚠ ENCRYPTION_KEY not set — API key encryption disabled (OK for fresh install with no providers)")
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func SchemaVersion() string { return schemaVersion }
|
|||||||
// Migrate runs all pending migrations for the current dialect.
|
// Migrate runs all pending migrations for the current dialect.
|
||||||
// Migration files live in migrations/<dialect>/ subdirectories.
|
// Migration files live in migrations/<dialect>/ subdirectories.
|
||||||
// Falls back to the root migrations/ directory if no subdirectory exists
|
// Falls back to the root migrations/ directory if no subdirectory exists
|
||||||
// (backward compatible with pre-v0.17.1 postgres-only layouts).
|
// (backward compatible with postgres-only layouts).
|
||||||
func Migrate() error {
|
func Migrate() error {
|
||||||
if DB == nil {
|
if DB == nil {
|
||||||
return fmt.Errorf("database not connected")
|
return fmt.Errorf("database not connected")
|
||||||
|
|||||||
@@ -117,10 +117,10 @@ func TestRouteFor(t *testing.T) {
|
|||||||
// Extension lifecycle
|
// Extension lifecycle
|
||||||
{"extension.loaded", DirLocal},
|
{"extension.loaded", DirLocal},
|
||||||
{"extension.error", DirLocal},
|
{"extension.error", DirLocal},
|
||||||
// Realtime (v0.5.0)
|
// Realtime
|
||||||
{"realtime.chat.message", DirToClient},
|
{"realtime.chat.message", DirToClient},
|
||||||
{"realtime.custom.event", DirToClient},
|
{"realtime.custom.event", DirToClient},
|
||||||
// Room management (v0.5.0)
|
// Room management
|
||||||
{"room.subscribe", DirFromClient},
|
{"room.subscribe", DirFromClient},
|
||||||
{"room.unsubscribe", DirFromClient},
|
{"room.unsubscribe", DirFromClient},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,16 +47,16 @@ var routeTable = map[string]Direction{
|
|||||||
// Model/provider status
|
// Model/provider status
|
||||||
"model.status": DirToClient,
|
"model.status": DirToClient,
|
||||||
|
|
||||||
// Role alerts (v0.17.0)
|
// Role alerts
|
||||||
"role.fallback": DirToClient,
|
"role.fallback": DirToClient,
|
||||||
|
|
||||||
// Notifications (v0.20.0)
|
// Notifications
|
||||||
"notification.new": DirToClient, // targeted via Hub.PublishToUser, not room-based
|
"notification.new": DirToClient, // targeted via Hub.PublishToUser, not room-based
|
||||||
"notification.read": DirToClient, // badge sync across tabs
|
"notification.read": DirToClient, // badge sync across tabs
|
||||||
|
|
||||||
// Workspace (v0.21.5)
|
// Workspace
|
||||||
|
|
||||||
// Workflow (v0.27.0, v0.3.2, v0.3.3)
|
// Workflow
|
||||||
"workflow.started": DirToClient, // instance started
|
"workflow.started": DirToClient, // instance started
|
||||||
"workflow.assigned": DirToClient, // new assignment → team members
|
"workflow.assigned": DirToClient, // new assignment → team members
|
||||||
"workflow.claimed": DirToClient, // assignment claimed → team + claimer
|
"workflow.claimed": DirToClient, // assignment claimed → team + claimer
|
||||||
@@ -64,10 +64,10 @@ var routeTable = map[string]Direction{
|
|||||||
"workflow.completed": DirToClient, // workflow finished → instance participants
|
"workflow.completed": DirToClient, // workflow finished → instance participants
|
||||||
"workflow.cancelled": DirToClient, // instance cancelled
|
"workflow.cancelled": DirToClient, // instance cancelled
|
||||||
"workflow.error": DirToClient, // engine/hook error
|
"workflow.error": DirToClient, // engine/hook error
|
||||||
"workflow.sla_breach": DirToClient, // SLA exceeded → team + admins (v0.3.3)
|
"workflow.sla_breach": DirToClient, // SLA exceeded → team + admins
|
||||||
"workflow.stale": DirToClient, // instance marked stale (v0.3.3)
|
"workflow.stale": DirToClient, // instance marked stale
|
||||||
"workflow.signoff": DirToClient, // signoff submitted (v0.3.4)
|
"workflow.signoff": DirToClient, // signoff submitted
|
||||||
"workflow.rejected": DirToClient, // rejection triggered cancel/reroute (v0.3.4)
|
"workflow.rejected": DirToClient, // rejection triggered cancel/reroute
|
||||||
|
|
||||||
// Plugin hooks — never cross the wire
|
// Plugin hooks — never cross the wire
|
||||||
"plugin.hook.": DirLocal,
|
"plugin.hook.": DirLocal,
|
||||||
@@ -82,14 +82,14 @@ var routeTable = map[string]Direction{
|
|||||||
"extension.loaded": DirLocal, // Client-only
|
"extension.loaded": DirLocal, // Client-only
|
||||||
"extension.error": DirLocal,
|
"extension.error": DirLocal,
|
||||||
|
|
||||||
// Trigger system (v0.2.2)
|
// Trigger system
|
||||||
"trigger.fired": DirLocal, // Trigger invocation event (observability)
|
"trigger.fired": DirLocal, // Trigger invocation event (observability)
|
||||||
"trigger.error": DirLocal, // Trigger execution error
|
"trigger.error": DirLocal, // Trigger execution error
|
||||||
|
|
||||||
// Realtime (v0.5.0): extension-published events, room-scoped
|
// Realtime: extension-published events, room-scoped
|
||||||
"realtime.": DirToClient,
|
"realtime.": DirToClient,
|
||||||
|
|
||||||
// Room management (v0.5.0): client room join/leave
|
// Room management: client room join/leave
|
||||||
"room.subscribe": DirFromClient,
|
"room.subscribe": DirFromClient,
|
||||||
"room.unsubscribe": DirFromClient,
|
"room.unsubscribe": DirFromClient,
|
||||||
|
|
||||||
|
|||||||
@@ -490,7 +490,7 @@ func BootstrapAdmin(cfg *config.Config, s store.Stores, uekCache ...*crypto.UEKC
|
|||||||
})
|
})
|
||||||
// If the actual password changed, the vault seal is stale. Probe it
|
// If the actual password changed, the vault seal is stale. Probe it
|
||||||
// with the current password and destroy only if unwrap fails.
|
// with the current password and destroy only if unwrap fails.
|
||||||
// Ensure handle exists (backfill for pre-v0.24.0 users)
|
// Ensure handle exists (backfill for users)
|
||||||
if existing.Handle == "" {
|
if existing.Handle == "" {
|
||||||
handle := auth.UniqueHandle(ctx, s.Users, models.HandleFromName(cfg.AdminUsername))
|
handle := auth.UniqueHandle(ctx, s.Users, models.HandleFromName(cfg.AdminUsername))
|
||||||
s.Users.Update(ctx, existing.ID, map[string]interface{}{"handle": handle})
|
s.Users.Update(ctx, existing.ID, map[string]interface{}{"handle": handle})
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"switchboard-core/models"
|
"switchboard-core/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ── Extension Dependencies (v0.38.2) ──────────────────
|
// ── Extension Dependencies ──────────────────
|
||||||
// Admin-only read endpoints for dependency graph visibility.
|
// Admin-only read endpoints for dependency graph visibility.
|
||||||
// Dependencies are created/deleted implicitly by InstallPackage/DeletePackage.
|
// Dependencies are created/deleted implicitly by InstallPackage/DeletePackage.
|
||||||
|
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ func hasPermission(granted []string, perm string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── api_schema support (v0.6.2) ─────────────────────────────────────
|
// ── api_schema support ─────────────────────────────────────
|
||||||
|
|
||||||
// APISchemaEntry represents one route's OpenAPI-level documentation
|
// APISchemaEntry represents one route's OpenAPI-level documentation
|
||||||
// declared via the optional manifest "api_schema" array.
|
// declared via the optional manifest "api_schema" array.
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ func (h *GroupHandler) AddMember(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify the added user (v0.20.0)
|
// Notify the added user
|
||||||
if svc := notifications.Default(); svc != nil {
|
if svc := notifications.Default(); svc != nil {
|
||||||
notifications.NotifyGroupMemberAdded(svc, req.UserID, groupID, group.Name)
|
notifications.NotifyGroupMemberAdded(svc, req.UserID, groupID, group.Name)
|
||||||
}
|
}
|
||||||
@@ -273,7 +273,7 @@ func (h *GroupHandler) RemoveMember(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify the removed user (v0.20.0)
|
// Notify the removed user
|
||||||
if svc := notifications.Default(); svc != nil && group != nil {
|
if svc := notifications.Default(); svc != nil && group != nil {
|
||||||
notifications.NotifyGroupMemberRemoved(svc, userID, groupID, group.Name)
|
notifications.NotifyGroupMemberRemoved(svc, userID, groupID, group.Name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func setupNotifHarness(t *testing.T) *notifHarness {
|
|||||||
protected.PUT("/notifications/preferences/:type", notifH.SetPreference)
|
protected.PUT("/notifications/preferences/:type", notifH.SetPreference)
|
||||||
protected.DELETE("/notifications/preferences/:type", notifH.DeletePreference)
|
protected.DELETE("/notifications/preferences/:type", notifH.DeletePreference)
|
||||||
|
|
||||||
// Admin broadcast route (v0.28.6)
|
// Admin broadcast route
|
||||||
admin := api.Group("/admin")
|
admin := api.Group("/admin")
|
||||||
admin.Use(middleware.Auth(cfg, stores.Users, userCache))
|
admin.Use(middleware.Auth(cfg, stores.Users, userCache))
|
||||||
admin.Use(middleware.RequireAdmin(stores))
|
admin.Use(middleware.RequireAdmin(stores))
|
||||||
@@ -560,7 +560,7 @@ func TestNotifications_Unauthenticated(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Admin Broadcast (v0.28.6) ────────────
|
// ── Admin Broadcast ────────────
|
||||||
|
|
||||||
func TestBroadcast_Success(t *testing.T) {
|
func TestBroadcast_Success(t *testing.T) {
|
||||||
h := setupNotifHarness(t)
|
h := setupNotifHarness(t)
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ func (h *NotificationHandler) Delete(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Notification Preferences (v0.20.0 Phase 3) ──
|
// ── Notification Preferences ──
|
||||||
|
|
||||||
// GET /api/v1/notifications/preferences
|
// GET /api/v1/notifications/preferences
|
||||||
func (h *NotificationHandler) ListPreferences(c *gin.Context) {
|
func (h *NotificationHandler) ListPreferences(c *gin.Context) {
|
||||||
@@ -282,7 +282,7 @@ func (h *NotificationHandler) DeletePreference(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Admin Broadcast (v0.28.6) ───────────────
|
// ── Admin Broadcast ───────────────
|
||||||
|
|
||||||
// POST /api/v1/admin/notifications/broadcast
|
// POST /api/v1/admin/notifications/broadcast
|
||||||
// Admin-only: sends a system.announcement notification to all active users.
|
// Admin-only: sends a system.announcement notification to all active users.
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
var validPackageID = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$`)
|
var validPackageID = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$`)
|
||||||
|
|
||||||
// PackageHandler manages unified package lifecycle (admin-only).
|
// PackageHandler manages unified package lifecycle (admin-only).
|
||||||
// Replaces SurfaceHandler (v0.28.7).
|
// Replaces SurfaceHandler.
|
||||||
type PackageHandler struct {
|
type PackageHandler struct {
|
||||||
stores store.Stores
|
stores store.Stores
|
||||||
packagesDir string // e.g. /data/packages — where archives are extracted
|
packagesDir string // e.g. /data/packages — where archives are extracted
|
||||||
@@ -43,12 +43,12 @@ func NewPackageHandler(s store.Stores, packagesDir ...string) *PackageHandler {
|
|||||||
return &PackageHandler{stores: s, packagesDir: dir}
|
return &PackageHandler{stores: s, packagesDir: dir}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSandbox attaches a Starlark sandbox for schema migrations (v0.30.0).
|
// SetSandbox attaches a Starlark sandbox for schema migrations.
|
||||||
func (h *PackageHandler) SetSandbox(sb *sandbox.Sandbox) {
|
func (h *PackageHandler) SetSandbox(sb *sandbox.Sandbox) {
|
||||||
h.sandbox = sb
|
h.sandbox = sb
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRunner attaches the Starlark runner for test-tool execution (v0.38.2).
|
// SetRunner attaches the Starlark runner for test-tool execution.
|
||||||
func (h *PackageHandler) SetRunner(r *sandbox.Runner) {
|
func (h *PackageHandler) SetRunner(r *sandbox.Runner) {
|
||||||
h.runner = r
|
h.runner = r
|
||||||
}
|
}
|
||||||
@@ -672,7 +672,7 @@ func extractableRelPath(name string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Package settings (v0.30.0 CS2) ──────────────
|
// ── Package settings ──────────────
|
||||||
|
|
||||||
// GetPackageSettings returns the settings schema from the manifest merged
|
// GetPackageSettings returns the settings schema from the manifest merged
|
||||||
// with current admin-configured values.
|
// with current admin-configured values.
|
||||||
@@ -806,7 +806,7 @@ func (h *PackageHandler) ListEnabledSurfaces(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{"data": enabled})
|
c.JSON(http.StatusOK, gin.H{"data": enabled})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Test Tool (v0.38.2) ─────────────────────────
|
// ── Test Tool ─────────────────────────
|
||||||
// POST /admin/packages/:id/test-tool
|
// POST /admin/packages/:id/test-tool
|
||||||
// Invokes a starlark extension's on_tool_call entry point directly,
|
// Invokes a starlark extension's on_tool_call entry point directly,
|
||||||
// without going through the AI chat loop. Admin-only test harness.
|
// without going through the AI chat loop. Admin-only test harness.
|
||||||
@@ -881,7 +881,7 @@ func (h *PackageHandler) TestTool(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Package update (v0.5.4) ──────────────────────
|
// ── Package update ──────────────────────
|
||||||
|
|
||||||
// UpdatePackage applies an in-place update to an existing package.
|
// UpdatePackage applies an in-place update to an existing package.
|
||||||
// POST /api/v1/admin/packages/:id/update
|
// POST /api/v1/admin/packages/:id/update
|
||||||
@@ -1158,7 +1158,7 @@ func mergePackageSettings(existing *store.PackageRegistration, manifest map[stri
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Package export (v0.5.4) ──────────────────────
|
// ── Package export ──────────────────────
|
||||||
|
|
||||||
// ExportPackage exports an installed package as a downloadable .pkg archive.
|
// ExportPackage exports an installed package as a downloadable .pkg archive.
|
||||||
// GET /api/v1/admin/packages/:id/export
|
// GET /api/v1/admin/packages/:id/export
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
// presence.go — Heartbeat upsert and status query (v0.23.1)
|
// presence.go — Heartbeat upsert and status query
|
||||||
//
|
//
|
||||||
// Clients POST /api/v1/presence/heartbeat every 30s while active.
|
// Clients POST /api/v1/presence/heartbeat every 30s while active.
|
||||||
// GET /api/v1/presence?users=id1,id2 returns current status.
|
// GET /api/v1/presence?users=id1,id2 returns current status.
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func (s *jsonScanner) Scan(src interface{}) error {
|
|||||||
// memory on the next rows.Scan() call, corrupting our data after the
|
// memory on the next rows.Scan() call, corrupting our data after the
|
||||||
// fact. This caused intermittent SafeJSON 500s on paginated list
|
// fact. This caused intermittent SafeJSON 500s on paginated list
|
||||||
// endpoints where row N's Settings pointed to row N+1's overwritten
|
// endpoints where row N's Settings pointed to row N+1's overwritten
|
||||||
// buffer. Fixed in v0.21.7.
|
// buffer. Fixed.
|
||||||
cp := make([]byte, len(v))
|
cp := make([]byte, len(v))
|
||||||
copy(cp, v)
|
copy(cp, v)
|
||||||
*s.dest = json.RawMessage(cp)
|
*s.dest = json.RawMessage(cp)
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ func TestSafeJSON_PaginatedWithCorrupt(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Driver buffer aliasing tests ────────────
|
// ── Driver buffer aliasing tests ────────────
|
||||||
// These verify the fix for the v0.21.7 corruption bug: scanJSON must
|
// These verify the fix for the buffer-aliasing corruption bug: scanJSON must
|
||||||
// copy []byte from the database driver, not alias it. Without the copy,
|
// copy []byte from the database driver, not alias it. Without the copy,
|
||||||
// rows.Next() can overwrite the buffer and corrupt previously-scanned
|
// rows.Next() can overwrite the buffer and corrupt previously-scanned
|
||||||
// json.RawMessage values in a paginated list.
|
// json.RawMessage values in a paginated list.
|
||||||
|
|||||||
@@ -419,7 +419,7 @@ func (h *TeamHandler) ListTeamAuditActions(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{"actions": actions})
|
c.JSON(http.StatusOK, gin.H{"actions": actions})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Team Roles API (v0.3.4) ─────────────────
|
// ── Team Roles API ─────────────────
|
||||||
|
|
||||||
// builtinRoles are always present in every team's role list.
|
// builtinRoles are always present in every team's role list.
|
||||||
var builtinRoles = []string{"admin", "member"}
|
var builtinRoles = []string{"admin", "member"}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"switchboard-core/workflow"
|
"switchboard-core/workflow"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ── Public Workflow Handlers (v0.3.3) ───────
|
// ── Public Workflow Handlers ───────
|
||||||
|
|
||||||
// WorkflowPublicHandler manages unauthenticated workflow entry endpoints.
|
// WorkflowPublicHandler manages unauthenticated workflow entry endpoints.
|
||||||
type WorkflowPublicHandler struct {
|
type WorkflowPublicHandler struct {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"switchboard-core/workflow"
|
"switchboard-core/workflow"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ── Signoff Handlers (v0.3.4) ─────────────────
|
// ── Signoff Handlers ─────────────────
|
||||||
|
|
||||||
// WorkflowSignoffHandler manages signoff HTTP endpoints.
|
// WorkflowSignoffHandler manages signoff HTTP endpoints.
|
||||||
type WorkflowSignoffHandler struct {
|
type WorkflowSignoffHandler struct {
|
||||||
|
|||||||
@@ -603,7 +603,7 @@ func TestWorkflowAssignment_Cancel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── v0.3.3 Store Tests ──────────────────────
|
// ── Instance Lifecycle Store Tests ──────────────────────
|
||||||
|
|
||||||
func TestWorkflowInstance_MarkStale(t *testing.T) {
|
func TestWorkflowInstance_MarkStale(t *testing.T) {
|
||||||
database.RequireTestDB(t)
|
database.RequireTestDB(t)
|
||||||
@@ -779,7 +779,7 @@ func TestWorkflowAssignment_ListByTeam(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Signoff store tests (v0.3.4) ──────────────
|
// ── Signoff store tests ──────────────
|
||||||
|
|
||||||
func TestWorkflowSignoff_CreateAndList(t *testing.T) {
|
func TestWorkflowSignoff_CreateAndList(t *testing.T) {
|
||||||
database.RequireTestDB(t)
|
database.RequireTestDB(t)
|
||||||
@@ -862,7 +862,7 @@ func TestWorkflowSignoff_ListEmpty(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Clone tests (v0.3.5) ──────────────────
|
// ── Clone tests ──────────────────
|
||||||
|
|
||||||
func TestWorkflow_Clone(t *testing.T) {
|
func TestWorkflow_Clone(t *testing.T) {
|
||||||
database.RequireTestDB(t)
|
database.RequireTestDB(t)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"switchboard-core/models"
|
"switchboard-core/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ── Team-Scoped Workflow Wrappers (v0.31.2) ────────────────
|
// ── Team-Scoped Workflow Wrappers ────────────────
|
||||||
//
|
//
|
||||||
// Thin wrappers that enforce team ownership before delegating
|
// Thin wrappers that enforce team ownership before delegating
|
||||||
// to the existing WorkflowHandler methods. Follows the same
|
// to the existing WorkflowHandler methods. Follows the same
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ func main() {
|
|||||||
log.Printf(" ⚠️ Extension SSRF protection relaxed: private IPs allowed")
|
log.Printf(" ⚠️ Extension SSRF protection relaxed: private IPs allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Bundled Packages (v0.3.8) ───────────────
|
// ── Bundled Packages ───────────────
|
||||||
// Auto-install bundled .pkg archives on first run.
|
// Auto-install bundled .pkg archives on first run.
|
||||||
// Skipped if SKIP_BUNDLED_PACKAGES=true or packages already exist.
|
// Skipped if SKIP_BUNDLED_PACKAGES=true or packages already exist.
|
||||||
if !cfg.SkipBundledPackages && stores.Packages != nil {
|
if !cfg.SkipBundledPackages && stores.Packages != nil {
|
||||||
@@ -178,7 +178,7 @@ func main() {
|
|||||||
handlers.InstallBundledPackages(cfg.BundledPackagesDir, bundledPkgDir, cfg.BundledPackages, stores, starlarkRunner)
|
handlers.InstallBundledPackages(cfg.BundledPackagesDir, bundledPkgDir, cfg.BundledPackages, stores, starlarkRunner)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Trigger Engine (v0.2.2) ─────────────────
|
// ── Trigger Engine ─────────────────
|
||||||
triggerEngine := triggers.New(stores, starlarkRunner, bus)
|
triggerEngine := triggers.New(stores, starlarkRunner, bus)
|
||||||
if err := triggerEngine.Start(context.Background()); err != nil {
|
if err := triggerEngine.Start(context.Background()); err != nil {
|
||||||
log.Printf(" ⚠️ Trigger engine start: %v", err)
|
log.Printf(" ⚠️ Trigger engine start: %v", err)
|
||||||
@@ -200,7 +200,7 @@ func main() {
|
|||||||
// ── WebSocket Hub ─────────────────────────
|
// ── WebSocket Hub ─────────────────────────
|
||||||
hub := events.NewHub(bus, middleware.GetAllowedOrigins(cfg))
|
hub := events.NewHub(bus, middleware.GetAllowedOrigins(cfg))
|
||||||
|
|
||||||
// ── Cluster Registry (v0.6.0) ────────────
|
// ── Cluster Registry ────────────
|
||||||
// PG-backed node self-registration + heartbeat. No-op on SQLite.
|
// PG-backed node self-registration + heartbeat. No-op on SQLite.
|
||||||
var clusterReg *cluster.Registry
|
var clusterReg *cluster.Registry
|
||||||
if database.IsPostgres() && stores.Cluster != nil {
|
if database.IsPostgres() && stores.Cluster != nil {
|
||||||
@@ -221,10 +221,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── WebSocket Ticket Store (v0.32.0: PG-backed for cross-pod) ─────
|
// ── WebSocket Ticket Store ─────
|
||||||
ticketAdapter := &events.TicketValidatorAdapter{Store: stores.Tickets}
|
ticketAdapter := &events.TicketValidatorAdapter{Store: stores.Tickets}
|
||||||
|
|
||||||
// ── Notification Service (v0.20.0) ───────
|
// ── Notification Service ───────
|
||||||
var notifSvc *notifications.Service
|
var notifSvc *notifications.Service
|
||||||
if stores.Notifications != nil {
|
if stores.Notifications != nil {
|
||||||
notifSvc = notifications.NewService(stores.Notifications, hub).
|
notifSvc = notifications.NewService(stores.Notifications, hub).
|
||||||
@@ -387,7 +387,7 @@ func main() {
|
|||||||
extH := handlers.NewExtensionHandler(stores)
|
extH := handlers.NewExtensionHandler(stores)
|
||||||
api.GET("/extensions/:id/assets/*path", extH.ServeExtensionAsset)
|
api.GET("/extensions/:id/assets/*path", extH.ServeExtensionAsset)
|
||||||
|
|
||||||
// ── Webhook Inbound (v0.2.2) ──────────────
|
// ── Webhook Inbound ──────────────
|
||||||
// Public routes — HMAC-verified in handler, no auth middleware.
|
// Public routes — HMAC-verified in handler, no auth middleware.
|
||||||
api.POST("/hooks/:package_id/:slug", triggerEngine.HandleWebhook)
|
api.POST("/hooks/:package_id/:slug", triggerEngine.HandleWebhook)
|
||||||
api.GET("/hooks/:package_id/:slug", triggerEngine.HandleWebhook)
|
api.GET("/hooks/:package_id/:slug", triggerEngine.HandleWebhook)
|
||||||
@@ -395,7 +395,7 @@ func main() {
|
|||||||
// ── Workflow Engine (shared by public + protected routes) ──
|
// ── Workflow Engine (shared by public + protected routes) ──
|
||||||
wfEngine := workflow.NewEngine(stores, bus, starlarkRunner)
|
wfEngine := workflow.NewEngine(stores, bus, starlarkRunner)
|
||||||
|
|
||||||
// ── Public Workflow Entry (v0.3.3) ─────
|
// ── Public Workflow Entry ─────
|
||||||
publicWfH := handlers.NewWorkflowPublicHandler(wfEngine, stores)
|
publicWfH := handlers.NewWorkflowPublicHandler(wfEngine, stores)
|
||||||
publicWf := api.Group("/public/workflows")
|
publicWf := api.Group("/public/workflows")
|
||||||
publicWf.Use(authLimiter.Limit())
|
publicWf.Use(authLimiter.Limit())
|
||||||
@@ -405,7 +405,7 @@ func main() {
|
|||||||
publicWf.POST("/advance/:token", publicWfH.AdvancePublic)
|
publicWf.POST("/advance/:token", publicWfH.AdvancePublic)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Workflow Scanner (v0.3.3) ──────────
|
// ── Workflow Scanner ──────────
|
||||||
wfScanner := workflow.NewScanner(stores, bus)
|
wfScanner := workflow.NewScanner(stores, bus)
|
||||||
wfScanner.Start()
|
wfScanner.Start()
|
||||||
defer wfScanner.Stop()
|
defer wfScanner.Stop()
|
||||||
@@ -415,7 +415,7 @@ func main() {
|
|||||||
protected.Use(middleware.Auth(cfg, stores.Users, userCache))
|
protected.Use(middleware.Auth(cfg, stores.Users, userCache))
|
||||||
protected.Use(middleware.ValidatePathParams())
|
protected.Use(middleware.ValidatePathParams())
|
||||||
{
|
{
|
||||||
// ── WebSocket Ticket (v0.28.8) ───────────
|
// ── WebSocket Ticket ───────────
|
||||||
// Issue a single-use ticket for WebSocket auth.
|
// Issue a single-use ticket for WebSocket auth.
|
||||||
// Client fetches this, then connects with ?ticket=<opaque>.
|
// Client fetches this, then connects with ?ticket=<opaque>.
|
||||||
protected.POST("/ws/ticket", func(c *gin.Context) {
|
protected.POST("/ws/ticket", func(c *gin.Context) {
|
||||||
@@ -428,15 +428,15 @@ func main() {
|
|||||||
c.JSON(http.StatusOK, gin.H{"ticket": ticket})
|
c.JSON(http.StatusOK, gin.H{"ticket": ticket})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Presence (v0.23.1)
|
// Presence
|
||||||
presence := handlers.NewPresenceHandler(stores)
|
presence := handlers.NewPresenceHandler(stores)
|
||||||
protected.POST("/presence/heartbeat", presence.Heartbeat)
|
protected.POST("/presence/heartbeat", presence.Heartbeat)
|
||||||
protected.GET("/presence", presence.Query)
|
protected.GET("/presence", presence.Query)
|
||||||
|
|
||||||
// User search (v0.23.2 — DM user picker)
|
// User search
|
||||||
protected.GET("/users/search", presence.SearchUsers)
|
protected.GET("/users/search", presence.SearchUsers)
|
||||||
|
|
||||||
// Workflows (v0.26.1 — team-owned staged processes)
|
// Workflows
|
||||||
wfH := handlers.NewWorkflowHandler(stores)
|
wfH := handlers.NewWorkflowHandler(stores)
|
||||||
protected.GET("/workflows", wfH.List)
|
protected.GET("/workflows", wfH.List)
|
||||||
protected.POST("/workflows", middleware.RequirePermission(auth.PermWorkflowCreate, stores), wfH.Create)
|
protected.POST("/workflows", middleware.RequirePermission(auth.PermWorkflowCreate, stores), wfH.Create)
|
||||||
@@ -452,7 +452,7 @@ func main() {
|
|||||||
protected.POST("/workflows/:id/clone", middleware.RequirePermission(auth.PermWorkflowCreate, stores), wfH.Clone)
|
protected.POST("/workflows/:id/clone", middleware.RequirePermission(auth.PermWorkflowCreate, stores), wfH.Clone)
|
||||||
protected.GET("/workflows/:id/versions/:version", wfH.GetVersion)
|
protected.GET("/workflows/:id/versions/:version", wfH.GetVersion)
|
||||||
|
|
||||||
// Workflow instances + assignments (v0.3.2)
|
// Workflow instances + assignments
|
||||||
wfInstH := handlers.NewWorkflowInstanceHandler(wfEngine, stores)
|
wfInstH := handlers.NewWorkflowInstanceHandler(wfEngine, stores)
|
||||||
protected.POST("/workflows/:id/instances", middleware.RequirePermission(auth.PermWorkflowSubmit, stores), wfInstH.Start)
|
protected.POST("/workflows/:id/instances", middleware.RequirePermission(auth.PermWorkflowSubmit, stores), wfInstH.Start)
|
||||||
protected.GET("/workflows/:id/instances", wfInstH.ListInstances)
|
protected.GET("/workflows/:id/instances", wfInstH.ListInstances)
|
||||||
@@ -467,16 +467,16 @@ func main() {
|
|||||||
protected.POST("/assignments/:id/cancel", middleware.RequirePermission(auth.PermWorkflowCreate, stores), wfAssignH.Cancel)
|
protected.POST("/assignments/:id/cancel", middleware.RequirePermission(auth.PermWorkflowCreate, stores), wfAssignH.Cancel)
|
||||||
protected.GET("/assignments/mine", wfAssignH.ListMine)
|
protected.GET("/assignments/mine", wfAssignH.ListMine)
|
||||||
|
|
||||||
// Workflow signoffs (v0.3.4)
|
// Workflow signoffs
|
||||||
wfSignoffH := handlers.NewWorkflowSignoffHandler(wfEngine, stores)
|
wfSignoffH := handlers.NewWorkflowSignoffHandler(wfEngine, stores)
|
||||||
protected.POST("/instances/:iid/signoffs", wfSignoffH.Submit)
|
protected.POST("/instances/:iid/signoffs", wfSignoffH.Submit)
|
||||||
protected.GET("/instances/:iid/signoffs", wfSignoffH.List)
|
protected.GET("/instances/:iid/signoffs", wfSignoffH.List)
|
||||||
|
|
||||||
// Surface discovery (v0.25.0, v0.28.7: unified packages)
|
// Surface discovery
|
||||||
pkgH := handlers.NewPackageHandler(stores)
|
pkgH := handlers.NewPackageHandler(stores)
|
||||||
protected.GET("/surfaces", pkgH.ListEnabledSurfaces)
|
protected.GET("/surfaces", pkgH.ListEnabledSurfaces)
|
||||||
|
|
||||||
// User package management (v0.30.0)
|
// User package management
|
||||||
userPkgDir := ""
|
userPkgDir := ""
|
||||||
if cfg.StoragePath != "" {
|
if cfg.StoragePath != "" {
|
||||||
userPkgDir = cfg.StoragePath + "/packages"
|
userPkgDir = cfg.StoragePath + "/packages"
|
||||||
@@ -486,7 +486,7 @@ func main() {
|
|||||||
protected.POST("/packages/install", userPkgH.InstallPersonalPackage)
|
protected.POST("/packages/install", userPkgH.InstallPersonalPackage)
|
||||||
protected.DELETE("/packages/:id", userPkgH.DeletePersonalPackage)
|
protected.DELETE("/packages/:id", userPkgH.DeletePersonalPackage)
|
||||||
|
|
||||||
// Connection Type Discovery (v0.38.4)
|
// Connection Type Discovery
|
||||||
connTypeH := handlers.NewConnectionTypeHandler(stores)
|
connTypeH := handlers.NewConnectionTypeHandler(stores)
|
||||||
protected.GET("/connection-types", connTypeH.ListConnectionTypes)
|
protected.GET("/connection-types", connTypeH.ListConnectionTypes)
|
||||||
|
|
||||||
@@ -507,21 +507,21 @@ func main() {
|
|||||||
protected.GET("/settings", settings.GetSettings)
|
protected.GET("/settings", settings.GetSettings)
|
||||||
protected.PUT("/settings", settings.UpdateSettings)
|
protected.PUT("/settings", settings.UpdateSettings)
|
||||||
|
|
||||||
// Documentation (v0.6.1)
|
// Documentation
|
||||||
docsDir := findDocsDir()
|
docsDir := findDocsDir()
|
||||||
docsH := handlers.NewDocsHandler(docsDir)
|
docsH := handlers.NewDocsHandler(docsDir)
|
||||||
protected.GET("/docs", docsH.ListDocs)
|
protected.GET("/docs", docsH.ListDocs)
|
||||||
protected.GET("/docs/:name", docsH.GetDoc)
|
protected.GET("/docs/:name", docsH.GetDoc)
|
||||||
|
|
||||||
// Permission bootstrap (v0.37.1) — self-service resolved permissions
|
// Permission bootstrap — self-service resolved permissions
|
||||||
permH := handlers.NewProfilePermissionsHandler(stores)
|
permH := handlers.NewProfilePermissionsHandler(stores)
|
||||||
protected.GET("/profile/permissions", permH.GetMyPermissions)
|
protected.GET("/profile/permissions", permH.GetMyPermissions)
|
||||||
|
|
||||||
// Boot payload (v0.37.15) — single-call SDK bootstrap
|
// Boot payload — single-call SDK bootstrap
|
||||||
bootH := handlers.NewProfileBootstrapHandler(stores)
|
bootH := handlers.NewProfileBootstrapHandler(stores)
|
||||||
protected.GET("/profile/bootstrap", bootH.GetBootstrap)
|
protected.GET("/profile/bootstrap", bootH.GetBootstrap)
|
||||||
|
|
||||||
// Notifications (v0.20.0)
|
// Notifications
|
||||||
notifH := handlers.NewNotificationHandler(stores, hub)
|
notifH := handlers.NewNotificationHandler(stores, hub)
|
||||||
protected.GET("/notifications", notifH.List)
|
protected.GET("/notifications", notifH.List)
|
||||||
protected.GET("/notifications/unread-count", notifH.UnreadCount)
|
protected.GET("/notifications/unread-count", notifH.UnreadCount)
|
||||||
@@ -529,12 +529,12 @@ func main() {
|
|||||||
protected.POST("/notifications/mark-all-read", notifH.MarkAllRead)
|
protected.POST("/notifications/mark-all-read", notifH.MarkAllRead)
|
||||||
protected.DELETE("/notifications/:id", notifH.Delete)
|
protected.DELETE("/notifications/:id", notifH.Delete)
|
||||||
|
|
||||||
// Notification preferences (v0.20.0 Phase 3)
|
// Notification preferences
|
||||||
protected.GET("/notifications/preferences", notifH.ListPreferences)
|
protected.GET("/notifications/preferences", notifH.ListPreferences)
|
||||||
protected.PUT("/notifications/preferences/:type", notifH.SetPreference)
|
protected.PUT("/notifications/preferences/:type", notifH.SetPreference)
|
||||||
protected.DELETE("/notifications/preferences/:type", notifH.DeletePreference)
|
protected.DELETE("/notifications/preferences/:type", notifH.DeletePreference)
|
||||||
|
|
||||||
// ── Scheduled Tasks — User (v0.2.2) ────
|
// ── Scheduled Tasks — User ────
|
||||||
userSchedH := handlers.NewScheduleHandler(stores, triggerEngine)
|
userSchedH := handlers.NewScheduleHandler(stores, triggerEngine)
|
||||||
protected.GET("/schedules", userSchedH.ListMySchedules)
|
protected.GET("/schedules", userSchedH.ListMySchedules)
|
||||||
protected.POST("/schedules", userSchedH.CreateSchedule)
|
protected.POST("/schedules", userSchedH.CreateSchedule)
|
||||||
@@ -566,18 +566,18 @@ func main() {
|
|||||||
// Team groups (team admins manage team-scoped groups)
|
// Team groups (team admins manage team-scoped groups)
|
||||||
teamScoped.GET("/groups", groupH.ListTeamGroups)
|
teamScoped.GET("/groups", groupH.ListTeamGroups)
|
||||||
|
|
||||||
// Team connections (v0.38.1)
|
// Team connections
|
||||||
teamScoped.GET("/connections", teams.ListTeamConnections)
|
teamScoped.GET("/connections", teams.ListTeamConnections)
|
||||||
teamScoped.POST("/connections", teams.CreateTeamConnection)
|
teamScoped.POST("/connections", teams.CreateTeamConnection)
|
||||||
teamScoped.PUT("/connections/:id", teams.UpdateTeamConnection)
|
teamScoped.PUT("/connections/:id", teams.UpdateTeamConnection)
|
||||||
teamScoped.DELETE("/connections/:id", teams.DeleteTeamConnection)
|
teamScoped.DELETE("/connections/:id", teams.DeleteTeamConnection)
|
||||||
|
|
||||||
// Team package management (v0.30.0)
|
// Team package management
|
||||||
teamPkgH := handlers.NewUserPackageHandler(stores, userPkgDir)
|
teamPkgH := handlers.NewUserPackageHandler(stores, userPkgDir)
|
||||||
teamScoped.POST("/packages/install", teamPkgH.InstallTeamPackage)
|
teamScoped.POST("/packages/install", teamPkgH.InstallTeamPackage)
|
||||||
teamScoped.DELETE("/packages/:id", teamPkgH.DeleteTeamPackage)
|
teamScoped.DELETE("/packages/:id", teamPkgH.DeleteTeamPackage)
|
||||||
|
|
||||||
// Team package settings — cascade overrides (v0.2.0)
|
// Team package settings — cascade overrides
|
||||||
teamPkgSettingsH := handlers.NewTeamPackageSettingsHandler(stores)
|
teamPkgSettingsH := handlers.NewTeamPackageSettingsHandler(stores)
|
||||||
teamScoped.GET("/packages/:id/settings", teamPkgSettingsH.GetTeamPackageSettings)
|
teamScoped.GET("/packages/:id/settings", teamPkgSettingsH.GetTeamPackageSettings)
|
||||||
teamScoped.PUT("/packages/:id/settings", teamPkgSettingsH.UpdateTeamPackageSettings)
|
teamScoped.PUT("/packages/:id/settings", teamPkgSettingsH.UpdateTeamPackageSettings)
|
||||||
@@ -587,7 +587,7 @@ func main() {
|
|||||||
teamScoped.GET("/audit", teams.ListTeamAuditLog)
|
teamScoped.GET("/audit", teams.ListTeamAuditLog)
|
||||||
teamScoped.GET("/audit/actions", teams.ListTeamAuditActions)
|
teamScoped.GET("/audit/actions", teams.ListTeamAuditActions)
|
||||||
|
|
||||||
// Team workflows — self-service (v0.31.2)
|
// Team workflows — self-service
|
||||||
teamWfH := handlers.NewWorkflowHandler(stores)
|
teamWfH := handlers.NewWorkflowHandler(stores)
|
||||||
teamScoped.GET("/workflows", teamWfH.ListTeamWorkflows)
|
teamScoped.GET("/workflows", teamWfH.ListTeamWorkflows)
|
||||||
teamScoped.POST("/workflows", teamWfH.CreateTeamWorkflow)
|
teamScoped.POST("/workflows", teamWfH.CreateTeamWorkflow)
|
||||||
@@ -605,7 +605,7 @@ func main() {
|
|||||||
teamScoped.GET("/workflows/available", teamWfH.ListGlobalWorkflows)
|
teamScoped.GET("/workflows/available", teamWfH.ListGlobalWorkflows)
|
||||||
teamScoped.GET("/workflows/:id/versions/:version", teamWfH.GetTeamWorkflowVersion)
|
teamScoped.GET("/workflows/:id/versions/:version", teamWfH.GetTeamWorkflowVersion)
|
||||||
|
|
||||||
// Team workflow instances + assignments (v0.3.2)
|
// Team workflow instances + assignments
|
||||||
teamWfInstH := handlers.NewWorkflowInstanceHandler(wfEngine, stores)
|
teamWfInstH := handlers.NewWorkflowInstanceHandler(wfEngine, stores)
|
||||||
teamScoped.POST("/workflows/:id/instances", teamWfInstH.Start)
|
teamScoped.POST("/workflows/:id/instances", teamWfInstH.Start)
|
||||||
teamScoped.GET("/workflows/:id/instances", teamWfInstH.ListInstances)
|
teamScoped.GET("/workflows/:id/instances", teamWfInstH.ListInstances)
|
||||||
@@ -616,7 +616,7 @@ func main() {
|
|||||||
teamWfAssignH := handlers.NewWorkflowAssignmentHandler(wfEngine, stores)
|
teamWfAssignH := handlers.NewWorkflowAssignmentHandler(wfEngine, stores)
|
||||||
teamScoped.GET("/assignments", teamWfAssignH.ListByTeam)
|
teamScoped.GET("/assignments", teamWfAssignH.ListByTeam)
|
||||||
|
|
||||||
// Team workflow signoffs (v0.3.4)
|
// Team workflow signoffs
|
||||||
teamWfSignoffH := handlers.NewWorkflowSignoffHandler(wfEngine, stores)
|
teamWfSignoffH := handlers.NewWorkflowSignoffHandler(wfEngine, stores)
|
||||||
teamScoped.POST("/instances/:iid/signoffs", teamWfSignoffH.Submit)
|
teamScoped.POST("/instances/:iid/signoffs", teamWfSignoffH.Submit)
|
||||||
teamScoped.GET("/instances/:iid/signoffs", teamWfSignoffH.List)
|
teamScoped.GET("/instances/:iid/signoffs", teamWfSignoffH.List)
|
||||||
@@ -660,7 +660,7 @@ func main() {
|
|||||||
// Stats
|
// Stats
|
||||||
admin.GET("/stats", adm.GetStats)
|
admin.GET("/stats", adm.GetStats)
|
||||||
|
|
||||||
// Global Connections (v0.38.1)
|
// Global Connections
|
||||||
admin.GET("/connections", adm.ListGlobalConnections)
|
admin.GET("/connections", adm.ListGlobalConnections)
|
||||||
admin.POST("/connections", adm.CreateGlobalConnection)
|
admin.POST("/connections", adm.CreateGlobalConnection)
|
||||||
admin.PUT("/connections/:id", adm.UpdateGlobalConnection)
|
admin.PUT("/connections/:id", adm.UpdateGlobalConnection)
|
||||||
@@ -678,7 +678,7 @@ func main() {
|
|||||||
admin.PUT("/teams/:id/members/:memberId", teamAdm.UpdateMember)
|
admin.PUT("/teams/:id/members/:memberId", teamAdm.UpdateMember)
|
||||||
admin.DELETE("/teams/:id/members/:memberId", teamAdm.RemoveMember)
|
admin.DELETE("/teams/:id/members/:memberId", teamAdm.RemoveMember)
|
||||||
|
|
||||||
// Admin broadcast (v0.28.6)
|
// Admin broadcast
|
||||||
adminNotifH := handlers.NewNotificationHandler(stores, hub)
|
adminNotifH := handlers.NewNotificationHandler(stores, hub)
|
||||||
admin.POST("/notifications/broadcast", adminNotifH.Broadcast)
|
admin.POST("/notifications/broadcast", adminNotifH.Broadcast)
|
||||||
|
|
||||||
@@ -697,7 +697,7 @@ func main() {
|
|||||||
admin.POST("/groups/:id/members", groupAdm.AddMember)
|
admin.POST("/groups/:id/members", groupAdm.AddMember)
|
||||||
admin.DELETE("/groups/:id/members/:userId", groupAdm.RemoveMember)
|
admin.DELETE("/groups/:id/members/:userId", groupAdm.RemoveMember)
|
||||||
|
|
||||||
// Permissions (v0.24.2)
|
// Permissions
|
||||||
admin.GET("/permissions", groupAdm.ListPermissions)
|
admin.GET("/permissions", groupAdm.ListPermissions)
|
||||||
admin.GET("/users/:id/permissions", groupAdm.GetUserPermissions)
|
admin.GET("/users/:id/permissions", groupAdm.GetUserPermissions)
|
||||||
|
|
||||||
@@ -714,7 +714,7 @@ func main() {
|
|||||||
// Vault
|
// Vault
|
||||||
admin.GET("/vault/status", adm.VaultStatus)
|
admin.GET("/vault/status", adm.VaultStatus)
|
||||||
|
|
||||||
// Email / SMTP test (v0.20.0 Phase 3)
|
// Email / SMTP test
|
||||||
emailAdm := handlers.NewAdminEmailHandler(stores)
|
emailAdm := handlers.NewAdminEmailHandler(stores)
|
||||||
admin.POST("/notifications/test-email", emailAdm.TestEmail)
|
admin.POST("/notifications/test-email", emailAdm.TestEmail)
|
||||||
|
|
||||||
@@ -739,7 +739,7 @@ func main() {
|
|||||||
admin.PUT("/extensions/:id/secrets", extSecH.SetSecrets)
|
admin.PUT("/extensions/:id/secrets", extSecH.SetSecrets)
|
||||||
admin.DELETE("/extensions/:id/secrets", extSecH.DeleteSecrets)
|
admin.DELETE("/extensions/:id/secrets", extSecH.DeleteSecrets)
|
||||||
|
|
||||||
// Package lifecycle management (v0.28.7 — replaces surfaces + extensions admin)
|
// Package lifecycle management
|
||||||
packagesDir := ""
|
packagesDir := ""
|
||||||
if cfg.StoragePath != "" {
|
if cfg.StoragePath != "" {
|
||||||
packagesDir = cfg.StoragePath + "/packages"
|
packagesDir = cfg.StoragePath + "/packages"
|
||||||
@@ -747,7 +747,7 @@ func main() {
|
|||||||
pkgAdm := handlers.NewPackageHandler(stores, packagesDir)
|
pkgAdm := handlers.NewPackageHandler(stores, packagesDir)
|
||||||
pkgAdm.SetSandbox(sandbox.New(sandbox.DefaultConfig()))
|
pkgAdm.SetSandbox(sandbox.New(sandbox.DefaultConfig()))
|
||||||
|
|
||||||
// Package registry — must be registered before /packages/:id (v0.30.0)
|
// Package registry — must be registered before /packages/:id
|
||||||
registryH := handlers.NewRegistryHandler(stores, packagesDir, pkgAdm)
|
registryH := handlers.NewRegistryHandler(stores, packagesDir, pkgAdm)
|
||||||
admin.GET("/packages/registry", registryH.BrowseRegistry)
|
admin.GET("/packages/registry", registryH.BrowseRegistry)
|
||||||
admin.POST("/packages/registry/install", registryH.InstallFromRegistry)
|
admin.POST("/packages/registry/install", registryH.InstallFromRegistry)
|
||||||
@@ -767,11 +767,11 @@ func main() {
|
|||||||
admin.GET("/packages/:id/export", pkgAdm.ExportPackage)
|
admin.GET("/packages/:id/export", pkgAdm.ExportPackage)
|
||||||
admin.GET("/dependencies", pkgAdm.ListAllDependencies)
|
admin.GET("/dependencies", pkgAdm.ListAllDependencies)
|
||||||
|
|
||||||
// Workflow package export (v0.30.2)
|
// Workflow package export
|
||||||
wfPkgH := handlers.NewWorkflowPackageHandler(stores)
|
wfPkgH := handlers.NewWorkflowPackageHandler(stores)
|
||||||
admin.GET("/workflows/:id/export", wfPkgH.ExportWorkflowPackage)
|
admin.GET("/workflows/:id/export", wfPkgH.ExportWorkflowPackage)
|
||||||
|
|
||||||
// ── Triggers (v0.2.2) ─────────────────
|
// ── Triggers ─────────────────
|
||||||
trigH := handlers.NewTriggerHandler(stores, triggerEngine)
|
trigH := handlers.NewTriggerHandler(stores, triggerEngine)
|
||||||
admin.GET("/triggers", trigH.ListTriggers)
|
admin.GET("/triggers", trigH.ListTriggers)
|
||||||
admin.GET("/triggers/:id", trigH.GetTrigger)
|
admin.GET("/triggers/:id", trigH.GetTrigger)
|
||||||
@@ -781,20 +781,20 @@ func main() {
|
|||||||
admin.GET("/triggers/:id/logs", trigH.ListTriggerLogs)
|
admin.GET("/triggers/:id/logs", trigH.ListTriggerLogs)
|
||||||
admin.GET("/packages/:id/triggers", trigH.ListPackageTriggers)
|
admin.GET("/packages/:id/triggers", trigH.ListPackageTriggers)
|
||||||
|
|
||||||
// ── Scheduled Tasks — Admin (v0.2.2) ──
|
// ── Scheduled Tasks — Admin ──
|
||||||
schedH := handlers.NewScheduleHandler(stores, triggerEngine)
|
schedH := handlers.NewScheduleHandler(stores, triggerEngine)
|
||||||
admin.GET("/schedules", schedH.AdminListSchedules)
|
admin.GET("/schedules", schedH.AdminListSchedules)
|
||||||
admin.PUT("/schedules/:id/enable", schedH.AdminEnableSchedule)
|
admin.PUT("/schedules/:id/enable", schedH.AdminEnableSchedule)
|
||||||
admin.PUT("/schedules/:id/disable", schedH.AdminDisableSchedule)
|
admin.PUT("/schedules/:id/disable", schedH.AdminDisableSchedule)
|
||||||
admin.DELETE("/schedules/:id", schedH.AdminDeleteSchedule)
|
admin.DELETE("/schedules/:id", schedH.AdminDeleteSchedule)
|
||||||
|
|
||||||
// ── Cluster (v0.6.0) ─────────────────
|
// ── Cluster ─────────────────
|
||||||
if stores.Cluster != nil {
|
if stores.Cluster != nil {
|
||||||
clusterH := handlers.NewClusterHandler(stores)
|
clusterH := handlers.NewClusterHandler(stores)
|
||||||
admin.GET("/cluster", clusterH.ListNodes)
|
admin.GET("/cluster", clusterH.ListNodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Backup/Restore (v0.6.1) ─────────
|
// ── Backup/Restore ─────────
|
||||||
backupH := handlers.NewBackupHandler(stores, packagesDir, cfg.StoragePath)
|
backupH := handlers.NewBackupHandler(stores, packagesDir, cfg.StoragePath)
|
||||||
admin.POST("/backup", backupH.CreateBackup)
|
admin.POST("/backup", backupH.CreateBackup)
|
||||||
admin.GET("/backups", backupH.ListBackups)
|
admin.GET("/backups", backupH.ListBackups)
|
||||||
@@ -834,7 +834,7 @@ func main() {
|
|||||||
pages.SetVersion(Version)
|
pages.SetVersion(Version)
|
||||||
pageEngine := pages.New(cfg, stores)
|
pageEngine := pages.New(cfg, stores)
|
||||||
|
|
||||||
// Root redirect → configurable default surface (v0.2.1)
|
// Root redirect → configurable default surface
|
||||||
base.GET("/", pageEngine.DefaultSurfaceRedirect())
|
base.GET("/", pageEngine.DefaultSurfaceRedirect())
|
||||||
|
|
||||||
// Login page — no auth required
|
// Login page — no auth required
|
||||||
@@ -847,7 +847,7 @@ func main() {
|
|||||||
middleware.AuthOrRedirect(cfg, stores.Users, userCache),
|
middleware.AuthOrRedirect(cfg, stores.Users, userCache),
|
||||||
middleware.RequireAdminPage(stores),
|
middleware.RequireAdminPage(stores),
|
||||||
},
|
},
|
||||||
Session: middleware.AuthOrRedirect(cfg, stores.Users, userCache), // TODO: session middleware for workflow visitors (v0.2.0)
|
Session: middleware.AuthOrRedirect(cfg, stores.Users, userCache), // TODO: session middleware for workflow visitors
|
||||||
})
|
})
|
||||||
|
|
||||||
// Mounted at /s/:slug/api/* with JWT auth (returns 401, not redirect).
|
// Mounted at /s/:slug/api/* with JWT auth (returns 401, not redirect).
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ type TicketValidator interface {
|
|||||||
// WsAuth returns a Gin middleware for the WebSocket endpoint.
|
// WsAuth returns a Gin middleware for the WebSocket endpoint.
|
||||||
// It authenticates via three methods in priority order:
|
// It authenticates via three methods in priority order:
|
||||||
//
|
//
|
||||||
// 1. ?ticket=<opaque> — single-use ticket (preferred, v0.28.8+)
|
// 1. ?ticket=<opaque> — single-use ticket (preferred)
|
||||||
// 2. ?token=<jwt> — legacy JWT in query param (deprecated)
|
// 2. ?token=<jwt> — legacy JWT in query param (deprecated)
|
||||||
// 3. Authorization header — standard Bearer JWT
|
// 3. Authorization header — standard Bearer JWT
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
// ── Extension Connections (v0.38.1) ──────────────────
|
// ── Extension Connections ──────────────────
|
||||||
|
|
||||||
// ExtConnection represents a scoped credential/endpoint configuration
|
// ExtConnection represents a scoped credential/endpoint configuration
|
||||||
// for extensions that integrate with external services. Same scope
|
// for extensions that integrate with external services. Same scope
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
// ── Extension Dependencies (v0.38.2) ──────────────────
|
// ── Extension Dependencies ──────────────────
|
||||||
|
|
||||||
// ExtDependency records that a consumer package depends on a library package.
|
// ExtDependency records that a consumer package depends on a library package.
|
||||||
// Created at consumer install time from the manifest's "dependencies" map.
|
// Created at consumer install time from the manifest's "dependencies" map.
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ func IntPtr(i int) *int { return &i }
|
|||||||
func BoolPtr(b bool) *bool { return &b }
|
func BoolPtr(b bool) *bool { return &b }
|
||||||
|
|
||||||
// =========================================
|
// =========================================
|
||||||
// GROUPS (v0.16.0)
|
// GROUPS
|
||||||
// =========================================
|
// =========================================
|
||||||
|
|
||||||
// Group scope constants (reuses ScopeGlobal, ScopeTeam from above)
|
// Group scope constants (reuses ScopeGlobal, ScopeTeam from above)
|
||||||
@@ -286,7 +286,7 @@ type ResourceGrant struct {
|
|||||||
// KBChunk is a text segment from a document with its embedding vector.
|
// KBChunk is a text segment from a document with its embedding vector.
|
||||||
// KBSearchResult is a single result from similarity search.
|
// KBSearchResult is a single result from similarity search.
|
||||||
// ChannelKB represents a knowledge base linked to a channel.
|
// ChannelKB represents a knowledge base linked to a channel.
|
||||||
// PROVIDER HEALTH (v0.22.0)
|
// PROVIDER HEALTH
|
||||||
|
|
||||||
// ProviderStatus represents the derived health state.
|
// ProviderStatus represents the derived health state.
|
||||||
|
|
||||||
@@ -294,10 +294,10 @@ type ResourceGrant struct {
|
|||||||
|
|
||||||
// ErrorRate returns the error fraction, or 0 if no requests.
|
// ErrorRate returns the error fraction, or 0 if no requests.
|
||||||
|
|
||||||
// CAPABILITY OVERRIDES (v0.22.0)
|
// CAPABILITY OVERRIDES
|
||||||
|
|
||||||
// CapabilityOverride is an admin correction for a model's capabilities.
|
// CapabilityOverride is an admin correction for a model's capabilities.
|
||||||
// ROUTING POLICIES (v0.22.2)
|
// ROUTING POLICIES
|
||||||
|
|
||||||
// RoutingPolicy is one routing rule that controls how requests are
|
// RoutingPolicy is one routing rule that controls how requests are
|
||||||
// dispatched to provider configs. Stored in the routing_policies table.
|
// dispatched to provider configs. Stored in the routing_policies table.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package models
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// =========================================
|
// =========================================
|
||||||
// NOTIFICATIONS (v0.20.0)
|
// NOTIFICATIONS
|
||||||
// =========================================
|
// =========================================
|
||||||
|
|
||||||
// Notification represents an in-app notification for a user.
|
// Notification represents an in-app notification for a user.
|
||||||
|
|||||||
@@ -120,21 +120,21 @@ var ValidAudiences = map[string]bool{
|
|||||||
|
|
||||||
// ── Typed Form Template ─────────────────────
|
// ── Typed Form Template ─────────────────────
|
||||||
|
|
||||||
// TypedFormTemplate is the structured form_template schema (v0.29.3).
|
// TypedFormTemplate is the structured form_template schema.
|
||||||
type TypedFormTemplate struct {
|
type TypedFormTemplate struct {
|
||||||
Fields []FormField `json:"fields"`
|
Fields []FormField `json:"fields"`
|
||||||
Fieldsets []FormFieldset `json:"fieldsets,omitempty"`
|
Fieldsets []FormFieldset `json:"fieldsets,omitempty"`
|
||||||
Hooks *FormHooks `json:"hooks,omitempty"`
|
Hooks *FormHooks `json:"hooks,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormFieldset groups fields into a labelled step for progressive forms (v0.35.0).
|
// FormFieldset groups fields into a labelled step for progressive forms.
|
||||||
// When fieldsets is present, top-level fields is ignored.
|
// When fieldsets is present, top-level fields is ignored.
|
||||||
type FormFieldset struct {
|
type FormFieldset struct {
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Fields []FormField `json:"fields"`
|
Fields []FormField `json:"fields"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FieldCondition controls conditional visibility of a form field (v0.35.0).
|
// FieldCondition controls conditional visibility of a form field.
|
||||||
// When set, the field is shown/validated only if the condition is met.
|
// When set, the field is shown/validated only if the condition is met.
|
||||||
type FieldCondition struct {
|
type FieldCondition struct {
|
||||||
When string `json:"when"` // key of the field to check
|
When string `json:"when"` // key of the field to check
|
||||||
@@ -389,7 +389,7 @@ func validateSelect(f FormField, val interface{}) []FieldError {
|
|||||||
return []FieldError{{Key: f.Key, Message: f.Label + " is not a valid option"}}
|
return []FieldError{{Key: f.Key, Message: f.Label + " is not a valid option"}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Field Condition Evaluator (v0.35.0) ─────
|
// ── Field Condition Evaluator ─────
|
||||||
|
|
||||||
// evaluateFieldCondition checks if a conditional field should be visible/validated.
|
// evaluateFieldCondition checks if a conditional field should be visible/validated.
|
||||||
func evaluateFieldCondition(cond *FieldCondition, data map[string]interface{}) bool {
|
func evaluateFieldCondition(cond *FieldCondition, data map[string]interface{}) bool {
|
||||||
@@ -502,7 +502,7 @@ type WorkflowAssignment struct {
|
|||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Workflow Signoff (v0.3.4) ───────────────
|
// ── Workflow Signoff ───────────────
|
||||||
|
|
||||||
// WorkflowSignoff records a user's approval or rejection at a stage boundary.
|
// WorkflowSignoff records a user's approval or rejection at a stage boundary.
|
||||||
type WorkflowSignoff struct {
|
type WorkflowSignoff struct {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
// ── Role Fallback ───────────────────────────
|
// ── Role Fallback ───────────────────────────
|
||||||
// Subscribes to the existing "role.fallback" EventBus event (emitted by
|
// Subscribes to the existing "role.fallback" EventBus event (emitted by
|
||||||
// capabilities/resolver.go since v0.17.0) and creates notifications for
|
// capabilities/resolver.go) and creates notifications for
|
||||||
// all admin users.
|
// all admin users.
|
||||||
|
|
||||||
// roleFallbackPayload matches the payload emitted by the role resolver.
|
// roleFallbackPayload matches the payload emitted by the role resolver.
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ type AdminPageData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SettingsPageData is what the settings surface templates receive.
|
// SettingsPageData is what the settings surface templates receive.
|
||||||
// Feature gates (BYOK, personas) moved to sw.auth.policies in v0.37.19.
|
// Feature gates (BYOK, personas) moved to sw.auth.policies.
|
||||||
type SettingsPageData struct {
|
type SettingsPageData struct {
|
||||||
Section string `json:"section"`
|
Section string `json:"section"`
|
||||||
ConfigSections []ConfigSectionEntry `json:"config_sections,omitempty"`
|
ConfigSections []ConfigSectionEntry `json:"config_sections,omitempty"`
|
||||||
@@ -116,7 +116,7 @@ func (e *Engine) settingsLoader(c *gin.Context, s store.Stores) (any, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Config section discovery (v0.38.3) ───────
|
// ── Config section discovery ───────
|
||||||
//
|
//
|
||||||
// Packages declare a config_section in their manifest. This helper scans
|
// Packages declare a config_section in their manifest. This helper scans
|
||||||
// enabled packages and returns entries whose surfaces array includes the
|
// enabled packages and returns entries whose surfaces array includes the
|
||||||
|
|||||||
@@ -629,7 +629,7 @@ type WorkflowLandingPageData struct {
|
|||||||
PersonaName string
|
PersonaName string
|
||||||
PersonaIcon string
|
PersonaIcon string
|
||||||
StageCount int
|
StageCount int
|
||||||
FirstStageMode string // custom | form_only | form_chat (v0.29.3)
|
FirstStageMode string // custom | form_only | form_chat
|
||||||
ResumeURL string // non-empty if visitor has an active session
|
ResumeURL string // non-empty if visitor has an active session
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -649,7 +649,7 @@ func (e *Engine) RenderWorkflow() gin.HandlerFunc {
|
|||||||
// Load session display name
|
// Load session display name
|
||||||
sessionName := "Visitor"
|
sessionName := "Visitor"
|
||||||
|
|
||||||
// Load workflow stage info for form rendering (v0.29.3)
|
// Load workflow stage info for form rendering
|
||||||
var stageMode, stageName, formTplJSON, surfacePkgID, brandingJSON string
|
var stageMode, stageName, formTplJSON, surfacePkgID, brandingJSON string
|
||||||
var totalStages, currentStage int
|
var totalStages, currentStage int
|
||||||
stageMode = "custom" // default
|
stageMode = "custom" // default
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ type RunContext struct {
|
|||||||
// ChannelID is the context identifier, if any.
|
// ChannelID is the context identifier, if any.
|
||||||
ChannelID string
|
ChannelID string
|
||||||
|
|
||||||
// TeamID is the team context for settings cascade resolution (v0.2.0).
|
// TeamID is the team context for settings cascade resolution.
|
||||||
// When set, team-scoped package settings are included in the cascade.
|
// When set, team-scoped package settings are included in the cascade.
|
||||||
TeamID string
|
TeamID string
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ type Runner struct {
|
|||||||
stores store.Stores
|
stores store.Stores
|
||||||
packagesDir string
|
packagesDir string
|
||||||
notifier NotificationSender // nil = notifications module unavailable
|
notifier NotificationSender // nil = notifications module unavailable
|
||||||
connResolver ConnectionResolver // nil = connections module unavailable (v0.38.1)
|
connResolver ConnectionResolver // nil = connections module unavailable
|
||||||
db *sql.DB // nil = db module unavailable
|
db *sql.DB // nil = db module unavailable
|
||||||
dbPostgres bool // true = use $N placeholders; false = use ?
|
dbPostgres bool // true = use $N placeholders; false = use ?
|
||||||
allowPrivateIPs bool
|
allowPrivateIPs bool
|
||||||
@@ -74,7 +74,7 @@ func (r *Runner) SetNotifier(n NotificationSender) {
|
|||||||
r.notifier = n
|
r.notifier = n
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetConnectionResolver attaches the connection resolver for the connections module (v0.38.1).
|
// SetConnectionResolver attaches the connection resolver for the connections module.
|
||||||
func (r *Runner) SetConnectionResolver(cr ConnectionResolver) {
|
func (r *Runner) SetConnectionResolver(cr ConnectionResolver) {
|
||||||
r.connResolver = cr
|
r.connResolver = cr
|
||||||
}
|
}
|
||||||
@@ -93,7 +93,7 @@ func (r *Runner) SetPackagesDir(dir string) {
|
|||||||
r.packagesDir = dir
|
r.packagesDir = dir
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBus attaches the event bus for the realtime module (v0.5.0).
|
// SetBus attaches the event bus for the realtime module.
|
||||||
func (r *Runner) SetBus(bus *events.Bus) {
|
func (r *Runner) SetBus(bus *events.Bus) {
|
||||||
r.bus = bus
|
r.bus = bus
|
||||||
}
|
}
|
||||||
@@ -280,7 +280,7 @@ func (r *Runner) buildModules(ctx context.Context, packageID string, manifest ma
|
|||||||
// The manifest is passed through so modules can read their config
|
// The manifest is passed through so modules can read their config
|
||||||
// (e.g., http module reads network_access, provider reads requires_provider).
|
// (e.g., http module reads network_access, provider reads requires_provider).
|
||||||
// The RunContext carries per-invocation state for user-scoped modules.
|
// The RunContext carries per-invocation state for user-scoped modules.
|
||||||
// The libContext enables lib.load() caching and cycle detection (v0.38.2).
|
// The libContext enables lib.load() caching and cycle detection.
|
||||||
func (r *Runner) buildModulesWithLibCtx(ctx context.Context, packageID string, manifest map[string]any, rc *RunContext, lc *libContext) (map[string]starlark.Value, error) {
|
func (r *Runner) buildModulesWithLibCtx(ctx context.Context, packageID string, manifest map[string]any, rc *RunContext, lc *libContext) (map[string]starlark.Value, error) {
|
||||||
if r.stores.ExtPermissions == nil {
|
if r.stores.ExtPermissions == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -344,7 +344,7 @@ func (r *Runner) buildModulesWithLibCtx(ctx context.Context, packageID string, m
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// A package reads its own admin + team + user settings (v0.2.0 cascade).
|
// A package reads its own admin + team + user settings.
|
||||||
userID := ""
|
userID := ""
|
||||||
teamID := ""
|
teamID := ""
|
||||||
if rc != nil {
|
if rc != nil {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ func workflowGetDef(ctx context.Context, stores store.Stores) func(*starlark.Thr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Instance Read API (v0.3.2) ──────────────
|
// ── Instance Read API ──────────────
|
||||||
|
|
||||||
func workflowGetInstance(ctx context.Context, stores store.Stores) func(*starlark.Thread, *starlark.Builtin, starlark.Tuple, []starlark.Tuple) (starlark.Value, error) {
|
func workflowGetInstance(ctx context.Context, stores store.Stores) func(*starlark.Thread, *starlark.Builtin, starlark.Tuple, []starlark.Tuple) (starlark.Value, error) {
|
||||||
return func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
return func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||||
@@ -186,5 +186,5 @@ func goValToStarlark(v interface{}) starlark.Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// workflowRoute routes the workflow to a named stage (v0.35.0).
|
// workflowRoute routes the workflow to a named stage.
|
||||||
// Starlark: workflow.route(channel_id, target_stage, reason)
|
// Starlark: workflow.route(channel_id, target_stage, reason)
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ type UserStore interface {
|
|||||||
RevokeAllRefreshTokens(ctx context.Context, userID string) error
|
RevokeAllRefreshTokens(ctx context.Context, userID string) error
|
||||||
CleanExpiredTokens(ctx context.Context) error
|
CleanExpiredTokens(ctx context.Context) error
|
||||||
|
|
||||||
// ── CS1 additions (v0.29.0) ──
|
// ── CS1 additions ──
|
||||||
|
|
||||||
// Exists returns true if a user with the given ID exists and is active.
|
// Exists returns true if a user with the given ID exists and is active.
|
||||||
Exists(ctx context.Context, userID string) (bool, error)
|
Exists(ctx context.Context, userID string) (bool, error)
|
||||||
@@ -94,7 +94,7 @@ type UserStore interface {
|
|||||||
// Excludes the calling user. Max 20 results.
|
// Excludes the calling user. Max 20 results.
|
||||||
SearchActive(ctx context.Context, excludeUserID, query string) ([]UserSearchResult, error)
|
SearchActive(ctx context.Context, excludeUserID, query string) ([]UserSearchResult, error)
|
||||||
|
|
||||||
// ── CS2 additions (v0.29.0) ──
|
// ── CS2 additions ──
|
||||||
|
|
||||||
// CountAll returns the total number of users.
|
// CountAll returns the total number of users.
|
||||||
CountAll(ctx context.Context) (int, error)
|
CountAll(ctx context.Context) (int, error)
|
||||||
@@ -108,7 +108,7 @@ type UserStore interface {
|
|||||||
// UpdateVaultKeys re-wraps the user's vault keys (password change).
|
// UpdateVaultKeys re-wraps the user's vault keys (password change).
|
||||||
UpdateVaultKeys(ctx context.Context, userID string, encUEK, salt, nonce []byte) error
|
UpdateVaultKeys(ctx context.Context, userID string, encUEK, salt, nonce []byte) error
|
||||||
|
|
||||||
// ── CS4 additions (v0.29.0) ──
|
// ── CS4 additions ──
|
||||||
|
|
||||||
// ClearVaultKeys nulls out vault columns and sets vault_set = false.
|
// ClearVaultKeys nulls out vault columns and sets vault_set = false.
|
||||||
ClearVaultKeys(ctx context.Context, userID string) error
|
ClearVaultKeys(ctx context.Context, userID string) error
|
||||||
@@ -147,7 +147,7 @@ type TeamStore interface {
|
|||||||
IsTeamAdmin(ctx context.Context, teamID, userID string) (bool, error)
|
IsTeamAdmin(ctx context.Context, teamID, userID string) (bool, error)
|
||||||
IsMember(ctx context.Context, teamID, userID string) (bool, error)
|
IsMember(ctx context.Context, teamID, userID string) (bool, error)
|
||||||
|
|
||||||
// ── CS1 additions (v0.29.0) ──
|
// ── CS1 additions ──
|
||||||
|
|
||||||
// Exists returns true if a team with the given ID exists.
|
// Exists returns true if a team with the given ID exists.
|
||||||
Exists(ctx context.Context, teamID string) (bool, error)
|
Exists(ctx context.Context, teamID string) (bool, error)
|
||||||
@@ -161,12 +161,12 @@ type TeamStore interface {
|
|||||||
// ListTeamAuditActions returns distinct audit actions for a team's members.
|
// ListTeamAuditActions returns distinct audit actions for a team's members.
|
||||||
ListTeamAuditActions(ctx context.Context, teamID string) ([]string, error)
|
ListTeamAuditActions(ctx context.Context, teamID string) ([]string, error)
|
||||||
|
|
||||||
// ── CS5b additions (v0.29.0) ──
|
// ── CS5b additions ──
|
||||||
|
|
||||||
// GetFirstTeamIDForUser returns the first team_id the user belongs to, or "" if none.
|
// GetFirstTeamIDForUser returns the first team_id the user belongs to, or "" if none.
|
||||||
GetFirstTeamIDForUser(ctx context.Context, userID string) (string, error)
|
GetFirstTeamIDForUser(ctx context.Context, userID string) (string, error)
|
||||||
|
|
||||||
// ── CS6 additions (v0.29.0) ──
|
// ── CS6 additions ──
|
||||||
|
|
||||||
// AddMemberReturningID inserts a team member and returns the row ID.
|
// AddMemberReturningID inserts a team member and returns the row ID.
|
||||||
AddMemberReturningID(ctx context.Context, teamID, userID, role string) (string, error)
|
AddMemberReturningID(ctx context.Context, teamID, userID, role string) (string, error)
|
||||||
@@ -207,7 +207,7 @@ type GlobalConfigStore interface {
|
|||||||
Set(ctx context.Context, key string, value models.JSONMap, updatedBy string) error
|
Set(ctx context.Context, key string, value models.JSONMap, updatedBy string) error
|
||||||
GetAll(ctx context.Context) (map[string]models.JSONMap, error)
|
GetAll(ctx context.Context) (map[string]models.JSONMap, error)
|
||||||
|
|
||||||
// ── OIDC state (v0.29.0-cs4) ──
|
// ── OIDC state ──
|
||||||
|
|
||||||
// SaveOIDCState stores a state+nonce pair for OIDC callback verification.
|
// SaveOIDCState stores a state+nonce pair for OIDC callback verification.
|
||||||
SaveOIDCState(ctx context.Context, state, nonce, redirectTo string) error
|
SaveOIDCState(ctx context.Context, state, nonce, redirectTo string) error
|
||||||
@@ -219,7 +219,7 @@ type GlobalConfigStore interface {
|
|||||||
// CleanupOIDCState removes stale OIDC states older than 10 minutes.
|
// CleanupOIDCState removes stale OIDC states older than 10 minutes.
|
||||||
CleanupOIDCState(ctx context.Context) error
|
CleanupOIDCState(ctx context.Context) error
|
||||||
|
|
||||||
// ── CS6 additions (v0.29.0) ──
|
// ── CS6 additions ──
|
||||||
|
|
||||||
// GetString returns the raw value column as a string for simple settings
|
// GetString returns the raw value column as a string for simple settings
|
||||||
// (e.g. bare JSON booleans like "true"/"false").
|
// (e.g. bare JSON booleans like "true"/"false").
|
||||||
@@ -227,7 +227,7 @@ type GlobalConfigStore interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =========================================
|
// =========================================
|
||||||
// GROUP STORE (v0.16.0)
|
// GROUP STORE
|
||||||
// =========================================
|
// =========================================
|
||||||
|
|
||||||
type GroupStore interface {
|
type GroupStore interface {
|
||||||
@@ -252,7 +252,7 @@ type GroupStore interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =========================================
|
// =========================================
|
||||||
// RESOURCE GRANT STORE (v0.16.0)
|
// RESOURCE GRANT STORE
|
||||||
// =========================================
|
// =========================================
|
||||||
|
|
||||||
type ResourceGrantStore interface {
|
type ResourceGrantStore interface {
|
||||||
@@ -265,7 +265,7 @@ type ResourceGrantStore interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =========================================
|
// =========================================
|
||||||
// NOTIFICATION STORE (v0.20.0)
|
// NOTIFICATION STORE
|
||||||
// =========================================
|
// =========================================
|
||||||
|
|
||||||
type NotificationStore interface {
|
type NotificationStore interface {
|
||||||
@@ -279,7 +279,7 @@ type NotificationStore interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =========================================
|
// =========================================
|
||||||
// NOTIFICATION PREFERENCES STORE (v0.20.0)
|
// NOTIFICATION PREFERENCES STORE
|
||||||
// =========================================
|
// =========================================
|
||||||
|
|
||||||
type NotificationPreferenceStore interface {
|
type NotificationPreferenceStore interface {
|
||||||
@@ -294,12 +294,12 @@ type NotificationPreferenceStore interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =========================================
|
// =========================================
|
||||||
// SESSION STORE (v0.24.3)
|
// SESSION STORE
|
||||||
// =========================================
|
// =========================================
|
||||||
|
|
||||||
|
|
||||||
// =========================================
|
// =========================================
|
||||||
// HEALTH STORE (v0.29.0)
|
// HEALTH STORE
|
||||||
// =========================================
|
// =========================================
|
||||||
|
|
||||||
// HealthStore manages provider health windows. The full interface is used
|
// HealthStore manages provider health windows. The full interface is used
|
||||||
@@ -310,7 +310,7 @@ type HealthStore interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =========================================
|
// =========================================
|
||||||
// PRESENCE STORE (v0.29.0)
|
// PRESENCE STORE
|
||||||
// =========================================
|
// =========================================
|
||||||
|
|
||||||
// PresenceStore manages user online/offline status.
|
// PresenceStore manages user online/offline status.
|
||||||
@@ -329,7 +329,7 @@ type PresenceStore interface {
|
|||||||
// =========================================
|
// =========================================
|
||||||
// SHARED TYPES
|
// SHARED TYPES
|
||||||
// =========================================
|
// =========================================
|
||||||
// CONNECTION STORE (v0.38.1)
|
// CONNECTION STORE
|
||||||
// =========================================
|
// =========================================
|
||||||
|
|
||||||
type ConnectionStore interface {
|
type ConnectionStore interface {
|
||||||
@@ -362,7 +362,7 @@ type ConnectionStore interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =========================================
|
// =========================================
|
||||||
// DEPENDENCY STORE (v0.38.2)
|
// DEPENDENCY STORE
|
||||||
// =========================================
|
// =========================================
|
||||||
|
|
||||||
type DependencyStore interface {
|
type DependencyStore interface {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// PackageStore manages the unified package registry (surfaces + extensions).
|
// PackageStore manages the unified package registry (surfaces + extensions).
|
||||||
// Replaces SurfaceRegistryStore and ExtensionStore (v0.28.7).
|
// Replaces SurfaceRegistryStore and ExtensionStore.
|
||||||
type PackageStore interface {
|
type PackageStore interface {
|
||||||
// ── Lifecycle (from SurfaceRegistryStore) ────────────
|
// ── Lifecycle (from SurfaceRegistryStore) ────────────
|
||||||
|
|
||||||
@@ -66,13 +66,13 @@ type PackageStore interface {
|
|||||||
// DeleteUserSettings removes per-user settings, reverting to defaults.
|
// DeleteUserSettings removes per-user settings, reverting to defaults.
|
||||||
DeleteUserSettings(ctx context.Context, pkgID, userID string) error
|
DeleteUserSettings(ctx context.Context, pkgID, userID string) error
|
||||||
|
|
||||||
// ── Scoped visibility (v0.30.0) ────────────────
|
// ── Scoped visibility ────────────────
|
||||||
|
|
||||||
// ListVisiblePackages returns packages visible to the given user:
|
// ListVisiblePackages returns packages visible to the given user:
|
||||||
// global packages + team packages for user's teams + personal packages.
|
// global packages + team packages for user's teams + personal packages.
|
||||||
ListVisiblePackages(ctx context.Context, userID string) ([]PackageRegistration, error)
|
ListVisiblePackages(ctx context.Context, userID string) ([]PackageRegistration, error)
|
||||||
|
|
||||||
// ── Package lifecycle (v0.30.0) ─────────────────
|
// ── Package lifecycle ─────────────────
|
||||||
|
|
||||||
// SetSchemaVersion updates the current schema version for a package.
|
// SetSchemaVersion updates the current schema version for a package.
|
||||||
SetSchemaVersion(ctx context.Context, id string, version int) error
|
SetSchemaVersion(ctx context.Context, id string, version int) error
|
||||||
@@ -83,7 +83,7 @@ type PackageStore interface {
|
|||||||
// SetPackageSettings stores admin-configured package-level settings.
|
// SetPackageSettings stores admin-configured package-level settings.
|
||||||
SetPackageSettings(ctx context.Context, id string, settings json.RawMessage) error
|
SetPackageSettings(ctx context.Context, id string, settings json.RawMessage) error
|
||||||
|
|
||||||
// ── Team-level settings (v0.2.0) ─────────────────
|
// ── Team-level settings ─────────────────
|
||||||
|
|
||||||
// GetTeamSettings returns team-scoped overrides for a package.
|
// GetTeamSettings returns team-scoped overrides for a package.
|
||||||
GetTeamSettings(ctx context.Context, pkgID, teamID string) (json.RawMessage, error)
|
GetTeamSettings(ctx context.Context, pkgID, teamID string) (json.RawMessage, error)
|
||||||
@@ -134,7 +134,7 @@ type PackageUserSettings struct {
|
|||||||
IsEnabled bool `json:"is_enabled" db:"is_enabled"`
|
IsEnabled bool `json:"is_enabled" db:"is_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PackageTeamSettings stores team-scoped overrides for a package (v0.2.0).
|
// PackageTeamSettings stores team-scoped overrides for a package.
|
||||||
type PackageTeamSettings struct {
|
type PackageTeamSettings struct {
|
||||||
PackageID string `json:"package_id" db:"package_id"`
|
PackageID string `json:"package_id" db:"package_id"`
|
||||||
TeamID string `json:"team_id" db:"team_id"`
|
TeamID string `json:"team_id" db:"team_id"`
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"switchboard-core/models"
|
"switchboard-core/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ── ConnectionStore (v0.38.1) ────────────────────────
|
// ── ConnectionStore ────────────────────────
|
||||||
|
|
||||||
type ConnectionStore struct{}
|
type ConnectionStore struct{}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"switchboard-core/models"
|
"switchboard-core/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ── DependencyStore (v0.38.2) ────────────────────────
|
// ── DependencyStore ────────────────────────
|
||||||
|
|
||||||
type DependencyStore struct{}
|
type DependencyStore struct{}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (s *GlobalConfigStore) GetAll(ctx context.Context) (map[string]models.JSONM
|
|||||||
return result, rows.Err()
|
return result, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── OIDC state (v0.29.0-cs4) ────────────────────────────────────────────
|
// ── OIDC state ────────────────────────────────────────────
|
||||||
|
|
||||||
func (s *GlobalConfigStore) SaveOIDCState(ctx context.Context, state, nonce, redirectTo string) error {
|
func (s *GlobalConfigStore) SaveOIDCState(ctx context.Context, state, nonce, redirectTo string) error {
|
||||||
_, err := DB.ExecContext(ctx, `
|
_, err := DB.ExecContext(ctx, `
|
||||||
@@ -86,7 +86,7 @@ func (s *GlobalConfigStore) CleanupOIDCState(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── CS6 additions (v0.29.0) ─────────────────────────────────────────────
|
// ── CS6 additions ─────────────────────────────────────────────
|
||||||
|
|
||||||
func (s *GlobalConfigStore) GetString(ctx context.Context, key string) (string, error) {
|
func (s *GlobalConfigStore) GetString(ctx context.Context, key string) (string, error) {
|
||||||
var val string
|
var val string
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ func (s *PackageStore) scanMany(ctx context.Context, query string, args ...inter
|
|||||||
return result, rows.Err()
|
return result, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Scoped visibility (v0.30.0) ──────────────────
|
// ── Scoped visibility ──────────────────
|
||||||
|
|
||||||
func (s *PackageStore) ListVisiblePackages(ctx context.Context, userID string) ([]store.PackageRegistration, error) {
|
func (s *PackageStore) ListVisiblePackages(ctx context.Context, userID string) ([]store.PackageRegistration, error) {
|
||||||
return s.scanMany(ctx, `
|
return s.scanMany(ctx, `
|
||||||
@@ -327,7 +327,7 @@ func (s *PackageStore) ListVisiblePackages(ctx context.Context, userID string) (
|
|||||||
ORDER BY p.source, p.title`, userID)
|
ORDER BY p.source, p.title`, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Package lifecycle (v0.30.0) ──────────────────
|
// ── Package lifecycle ──────────────────
|
||||||
|
|
||||||
func (s *PackageStore) SetSchemaVersion(ctx context.Context, id string, version int) error {
|
func (s *PackageStore) SetSchemaVersion(ctx context.Context, id string, version int) error {
|
||||||
result, err := DB.ExecContext(ctx,
|
result, err := DB.ExecContext(ctx,
|
||||||
@@ -367,7 +367,7 @@ func (s *PackageStore) SetPackageSettings(ctx context.Context, id string, settin
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Team-level settings (v0.2.0) ─────────────────
|
// ── Team-level settings ─────────────────
|
||||||
|
|
||||||
func (s *PackageStore) GetTeamSettings(ctx context.Context, pkgID, teamID string) (json.RawMessage, error) {
|
func (s *PackageStore) GetTeamSettings(ctx context.Context, pkgID, teamID string) (json.RawMessage, error) {
|
||||||
var settings []byte
|
var settings []byte
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ func (s *TeamStore) IsMember(ctx context.Context, teamID, userID string) (bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ── CS1 additions (v0.29.0) ─────────────────────────────────────────────
|
// ── CS1 additions ─────────────────────────────────────────────
|
||||||
|
|
||||||
func (s *TeamStore) Exists(ctx context.Context, teamID string) (bool, error) {
|
func (s *TeamStore) Exists(ctx context.Context, teamID string) (bool, error) {
|
||||||
var exists bool
|
var exists bool
|
||||||
@@ -279,7 +279,7 @@ func (s *TeamStore) ListTeamAuditActions(ctx context.Context, teamID string) ([]
|
|||||||
return actions, rows.Err()
|
return actions, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── CS5b additions (v0.29.0) ────────────────────────────────────────────
|
// ── CS5b additions ────────────────────────────────────────────
|
||||||
|
|
||||||
func (s *TeamStore) GetFirstTeamIDForUser(ctx context.Context, userID string) (string, error) {
|
func (s *TeamStore) GetFirstTeamIDForUser(ctx context.Context, userID string) (string, error) {
|
||||||
var teamID string
|
var teamID string
|
||||||
@@ -291,7 +291,7 @@ func (s *TeamStore) GetFirstTeamIDForUser(ctx context.Context, userID string) (s
|
|||||||
return teamID, nil
|
return teamID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── CS6 additions (v0.29.0) ────────────────────────────────────────────
|
// ── CS6 additions ────────────────────────────────────────────
|
||||||
|
|
||||||
func (s *TeamStore) AddMemberReturningID(ctx context.Context, teamID, userID, role string) (string, error) {
|
func (s *TeamStore) AddMemberReturningID(ctx context.Context, teamID, userID, role string) (string, error) {
|
||||||
var id string
|
var id string
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ func scanOneUser(ctx context.Context, query string, args ...interface{}) (*model
|
|||||||
ScanJSON(sj, &u.Settings)
|
ScanJSON(sj, &u.Settings)
|
||||||
return &u, nil
|
return &u, nil
|
||||||
}
|
}
|
||||||
// ── CS1 additions (v0.29.0) ─────────────────────────────────────────────
|
// ── CS1 additions ─────────────────────────────────────────────
|
||||||
|
|
||||||
func (s *UserStore) Exists(ctx context.Context, userID string) (bool, error) {
|
func (s *UserStore) Exists(ctx context.Context, userID string) (bool, error) {
|
||||||
var exists bool
|
var exists bool
|
||||||
@@ -231,7 +231,7 @@ func (s *UserStore) SearchActive(ctx context.Context, excludeUserID, query strin
|
|||||||
return results, rows.Err()
|
return results, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── CS2 additions (v0.29.0) ─────────────────────────────────────────────
|
// ── CS2 additions ─────────────────────────────────────────────
|
||||||
|
|
||||||
func (s *UserStore) MergeSettings(ctx context.Context, userID string, patch []byte) error {
|
func (s *UserStore) MergeSettings(ctx context.Context, userID string, patch []byte) error {
|
||||||
_, err := DB.ExecContext(ctx, `
|
_, err := DB.ExecContext(ctx, `
|
||||||
@@ -268,7 +268,7 @@ func (s *UserStore) CountAll(ctx context.Context) (int, error) {
|
|||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── CS4 additions (v0.29.0) ─────────────────────────────────────────────
|
// ── CS4 additions ─────────────────────────────────────────────
|
||||||
|
|
||||||
func (s *UserStore) ClearVaultKeys(ctx context.Context, userID string) error {
|
func (s *UserStore) ClearVaultKeys(ctx context.Context, userID string) error {
|
||||||
_, err := DB.ExecContext(ctx, `
|
_, err := DB.ExecContext(ctx, `
|
||||||
|
|||||||
@@ -404,7 +404,7 @@ func nullIfEmpty(s string) interface{} {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Instances (v0.3.1) ──────────────────────
|
// ── Instances ──────────────────────
|
||||||
|
|
||||||
func (s *WorkflowStore) CreateInstance(ctx context.Context, inst *models.WorkflowInstance) error {
|
func (s *WorkflowStore) CreateInstance(ctx context.Context, inst *models.WorkflowInstance) error {
|
||||||
stageData := jsonOrEmpty(inst.StageData)
|
stageData := jsonOrEmpty(inst.StageData)
|
||||||
@@ -573,7 +573,7 @@ func (s *WorkflowStore) ListActiveInstances(ctx context.Context) ([]models.Workf
|
|||||||
return result, rows.Err()
|
return result, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Assignments (v0.3.1) ────────────────────
|
// ── Assignments ────────────────────
|
||||||
|
|
||||||
func (s *WorkflowStore) CreateAssignment(ctx context.Context, a *models.WorkflowAssignment) error {
|
func (s *WorkflowStore) CreateAssignment(ctx context.Context, a *models.WorkflowAssignment) error {
|
||||||
reviewData := jsonOrEmpty(a.ReviewData)
|
reviewData := jsonOrEmpty(a.ReviewData)
|
||||||
@@ -661,7 +661,7 @@ func (s *WorkflowStore) ListAssignmentsByUser(ctx context.Context, userID string
|
|||||||
return s.queryAssignments(ctx, q, args...)
|
return s.queryAssignments(ctx, q, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Signoffs (v0.3.4) ─────────────────────────
|
// ── Signoffs ─────────────────────────
|
||||||
|
|
||||||
func (s *WorkflowStore) CreateSignoff(ctx context.Context, so *models.WorkflowSignoff) error {
|
func (s *WorkflowStore) CreateSignoff(ctx context.Context, so *models.WorkflowSignoff) error {
|
||||||
return DB.QueryRowContext(ctx, `
|
return DB.QueryRowContext(ctx, `
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"switchboard-core/store"
|
"switchboard-core/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ── ConnectionStore (v0.38.1) ────────────────────────
|
// ── ConnectionStore ────────────────────────
|
||||||
|
|
||||||
type ConnectionStore struct{}
|
type ConnectionStore struct{}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"switchboard-core/models"
|
"switchboard-core/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ── DependencyStore (v0.38.2) ────────────────────────
|
// ── DependencyStore ────────────────────────
|
||||||
|
|
||||||
type DependencyStore struct{}
|
type DependencyStore struct{}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func (s *GlobalConfigStore) GetAll(ctx context.Context) (map[string]models.JSONM
|
|||||||
return result, rows.Err()
|
return result, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── OIDC state (v0.29.0-cs4) ────────────────────────────────────────────
|
// ── OIDC state ────────────────────────────────────────────
|
||||||
|
|
||||||
func (s *GlobalConfigStore) SaveOIDCState(ctx context.Context, state, nonce, redirectTo string) error {
|
func (s *GlobalConfigStore) SaveOIDCState(ctx context.Context, state, nonce, redirectTo string) error {
|
||||||
_, err := DB.ExecContext(ctx, `
|
_, err := DB.ExecContext(ctx, `
|
||||||
@@ -84,7 +84,7 @@ func (s *GlobalConfigStore) CleanupOIDCState(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── CS6 additions (v0.29.0) ─────────────────────────────────────────────
|
// ── CS6 additions ─────────────────────────────────────────────
|
||||||
|
|
||||||
func (s *GlobalConfigStore) GetString(ctx context.Context, key string) (string, error) {
|
func (s *GlobalConfigStore) GetString(ctx context.Context, key string) (string, error) {
|
||||||
var val string
|
var val string
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ func (s *PackageStore) scanMany(ctx context.Context, query string, args ...inter
|
|||||||
return result, rows.Err()
|
return result, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Scoped visibility (v0.30.0) ──────────────────
|
// ── Scoped visibility ──────────────────
|
||||||
|
|
||||||
func (s *PackageStore) ListVisiblePackages(ctx context.Context, userID string) ([]store.PackageRegistration, error) {
|
func (s *PackageStore) ListVisiblePackages(ctx context.Context, userID string) ([]store.PackageRegistration, error) {
|
||||||
return s.scanMany(ctx, `
|
return s.scanMany(ctx, `
|
||||||
@@ -348,7 +348,7 @@ func (s *PackageStore) ListVisiblePackages(ctx context.Context, userID string) (
|
|||||||
ORDER BY p.source, p.title`, userID, userID)
|
ORDER BY p.source, p.title`, userID, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Package lifecycle (v0.30.0) ──────────────────
|
// ── Package lifecycle ──────────────────
|
||||||
|
|
||||||
func (s *PackageStore) SetSchemaVersion(ctx context.Context, id string, version int) error {
|
func (s *PackageStore) SetSchemaVersion(ctx context.Context, id string, version int) error {
|
||||||
result, err := DB.ExecContext(ctx,
|
result, err := DB.ExecContext(ctx,
|
||||||
@@ -388,7 +388,7 @@ func (s *PackageStore) SetPackageSettings(ctx context.Context, id string, settin
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Team-level settings (v0.2.0) ─────────────────
|
// ── Team-level settings ─────────────────
|
||||||
|
|
||||||
func (s *PackageStore) GetTeamSettings(ctx context.Context, pkgID, teamID string) (json.RawMessage, error) {
|
func (s *PackageStore) GetTeamSettings(ctx context.Context, pkgID, teamID string) (json.RawMessage, error) {
|
||||||
var settings string
|
var settings string
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ func (s *TeamStore) IsMember(ctx context.Context, teamID, userID string) (bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ── CS1 additions (v0.29.0) ─────────────────────────────────────────────
|
// ── CS1 additions ─────────────────────────────────────────────
|
||||||
|
|
||||||
func (s *TeamStore) Exists(ctx context.Context, teamID string) (bool, error) {
|
func (s *TeamStore) Exists(ctx context.Context, teamID string) (bool, error) {
|
||||||
var count int
|
var count int
|
||||||
@@ -286,7 +286,7 @@ func (s *TeamStore) ListTeamAuditActions(ctx context.Context, teamID string) ([]
|
|||||||
return actions, rows.Err()
|
return actions, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── CS5b additions (v0.29.0) ────────────────────────────────────────────
|
// ── CS5b additions ────────────────────────────────────────────
|
||||||
|
|
||||||
func (s *TeamStore) GetFirstTeamIDForUser(ctx context.Context, userID string) (string, error) {
|
func (s *TeamStore) GetFirstTeamIDForUser(ctx context.Context, userID string) (string, error) {
|
||||||
var teamID string
|
var teamID string
|
||||||
@@ -298,7 +298,7 @@ func (s *TeamStore) GetFirstTeamIDForUser(ctx context.Context, userID string) (s
|
|||||||
return teamID, nil
|
return teamID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── CS6 additions (v0.29.0) ────────────────────────────────────────────
|
// ── CS6 additions ────────────────────────────────────────────
|
||||||
|
|
||||||
func (s *TeamStore) AddMemberReturningID(ctx context.Context, teamID, userID, role string) (string, error) {
|
func (s *TeamStore) AddMemberReturningID(ctx context.Context, teamID, userID, role string) (string, error) {
|
||||||
id := store.NewID()
|
id := store.NewID()
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ func (s *UserStore) scanOne(ctx context.Context, query string, args ...interface
|
|||||||
ScanJSON(sj, &u.Settings)
|
ScanJSON(sj, &u.Settings)
|
||||||
return &u, nil
|
return &u, nil
|
||||||
}
|
}
|
||||||
// ── CS1 additions (v0.29.0) ─────────────────────────────────────────────
|
// ── CS1 additions ─────────────────────────────────────────────
|
||||||
|
|
||||||
func (s *UserStore) Exists(ctx context.Context, userID string) (bool, error) {
|
func (s *UserStore) Exists(ctx context.Context, userID string) (bool, error) {
|
||||||
var count int
|
var count int
|
||||||
@@ -238,7 +238,7 @@ func (s *UserStore) SearchActive(ctx context.Context, excludeUserID, query strin
|
|||||||
return results, rows.Err()
|
return results, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── CS2 additions (v0.29.0) ─────────────────────────────────────────────
|
// ── CS2 additions ─────────────────────────────────────────────
|
||||||
|
|
||||||
func (s *UserStore) MergeSettings(ctx context.Context, userID string, patch []byte) error {
|
func (s *UserStore) MergeSettings(ctx context.Context, userID string, patch []byte) error {
|
||||||
_, err := DB.ExecContext(ctx, `
|
_, err := DB.ExecContext(ctx, `
|
||||||
@@ -275,7 +275,7 @@ func (s *UserStore) CountAll(ctx context.Context) (int, error) {
|
|||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── CS4 additions (v0.29.0) ─────────────────────────────────────────────
|
// ── CS4 additions ─────────────────────────────────────────────
|
||||||
|
|
||||||
func (s *UserStore) ClearVaultKeys(ctx context.Context, userID string) error {
|
func (s *UserStore) ClearVaultKeys(ctx context.Context, userID string) error {
|
||||||
_, err := DB.ExecContext(ctx, `
|
_, err := DB.ExecContext(ctx, `
|
||||||
|
|||||||
@@ -418,7 +418,7 @@ func nullIfEmpty(s string) interface{} {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Instances (v0.3.1) ──────────────────────
|
// ── Instances ──────────────────────
|
||||||
|
|
||||||
func (s *WorkflowStore) CreateInstance(ctx context.Context, inst *models.WorkflowInstance) error {
|
func (s *WorkflowStore) CreateInstance(ctx context.Context, inst *models.WorkflowInstance) error {
|
||||||
inst.ID = store.NewID()
|
inst.ID = store.NewID()
|
||||||
@@ -597,7 +597,7 @@ func (s *WorkflowStore) ListActiveInstances(ctx context.Context) ([]models.Workf
|
|||||||
return result, rows.Err()
|
return result, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Assignments (v0.3.1) ────────────────────
|
// ── Assignments ────────────────────
|
||||||
|
|
||||||
func (s *WorkflowStore) CreateAssignment(ctx context.Context, a *models.WorkflowAssignment) error {
|
func (s *WorkflowStore) CreateAssignment(ctx context.Context, a *models.WorkflowAssignment) error {
|
||||||
a.ID = store.NewID()
|
a.ID = store.NewID()
|
||||||
@@ -690,7 +690,7 @@ func (s *WorkflowStore) ListAssignmentsByUser(ctx context.Context, userID string
|
|||||||
return s.queryAssignments(ctx, q, args...)
|
return s.queryAssignments(ctx, q, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Signoffs (v0.3.4) ─────────────────────────
|
// ── Signoffs ─────────────────────────
|
||||||
|
|
||||||
func (s *WorkflowStore) CreateSignoff(ctx context.Context, so *models.WorkflowSignoff) error {
|
func (s *WorkflowStore) CreateSignoff(ctx context.Context, so *models.WorkflowSignoff) error {
|
||||||
so.ID = store.NewID()
|
so.ID = store.NewID()
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ type WorkflowStore interface {
|
|||||||
GetVersion(ctx context.Context, workflowID string, versionNumber int) (*models.WorkflowVersion, error)
|
GetVersion(ctx context.Context, workflowID string, versionNumber int) (*models.WorkflowVersion, error)
|
||||||
GetLatestVersion(ctx context.Context, workflowID string) (*models.WorkflowVersion, error)
|
GetLatestVersion(ctx context.Context, workflowID string) (*models.WorkflowVersion, error)
|
||||||
|
|
||||||
// Instances (v0.3.1+)
|
// Instances
|
||||||
CreateInstance(ctx context.Context, inst *models.WorkflowInstance) error
|
CreateInstance(ctx context.Context, inst *models.WorkflowInstance) error
|
||||||
GetInstance(ctx context.Context, id string) (*models.WorkflowInstance, error)
|
GetInstance(ctx context.Context, id string) (*models.WorkflowInstance, error)
|
||||||
GetInstanceByToken(ctx context.Context, token string) (*models.WorkflowInstance, error)
|
GetInstanceByToken(ctx context.Context, token string) (*models.WorkflowInstance, error)
|
||||||
@@ -43,7 +43,7 @@ type WorkflowStore interface {
|
|||||||
MarkInstanceStale(ctx context.Context, id string) error
|
MarkInstanceStale(ctx context.Context, id string) error
|
||||||
ListActiveInstances(ctx context.Context) ([]models.WorkflowInstance, error)
|
ListActiveInstances(ctx context.Context) ([]models.WorkflowInstance, error)
|
||||||
|
|
||||||
// Assignments (v0.3.1)
|
// Assignments
|
||||||
CreateAssignment(ctx context.Context, a *models.WorkflowAssignment) error
|
CreateAssignment(ctx context.Context, a *models.WorkflowAssignment) error
|
||||||
ClaimAssignment(ctx context.Context, id string, userID string) error
|
ClaimAssignment(ctx context.Context, id string, userID string) error
|
||||||
UnclaimAssignment(ctx context.Context, id string) error
|
UnclaimAssignment(ctx context.Context, id string) error
|
||||||
@@ -53,7 +53,7 @@ type WorkflowStore interface {
|
|||||||
ListAssignmentsByInstance(ctx context.Context, instanceID string) ([]models.WorkflowAssignment, error)
|
ListAssignmentsByInstance(ctx context.Context, instanceID string) ([]models.WorkflowAssignment, error)
|
||||||
ListAssignmentsByUser(ctx context.Context, userID string, status string) ([]models.WorkflowAssignment, error)
|
ListAssignmentsByUser(ctx context.Context, userID string, status string) ([]models.WorkflowAssignment, error)
|
||||||
|
|
||||||
// Signoffs (v0.3.4)
|
// Signoffs
|
||||||
CreateSignoff(ctx context.Context, s *models.WorkflowSignoff) error
|
CreateSignoff(ctx context.Context, s *models.WorkflowSignoff) error
|
||||||
ListSignoffs(ctx context.Context, instanceID, stage string) ([]models.WorkflowSignoff, error)
|
ListSignoffs(ctx context.Context, instanceID, stage string) ([]models.WorkflowSignoff, error)
|
||||||
CountSignoffs(ctx context.Context, instanceID, stage, decision string) (int, error)
|
CountSignoffs(ctx context.Context, instanceID, stage, decision string) (int, error)
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ func (e *Engine) advanceInternal(ctx context.Context, instanceID string, stageDa
|
|||||||
}
|
}
|
||||||
currentStage := stages[currentOrdinal]
|
currentStage := stages[currentOrdinal]
|
||||||
|
|
||||||
// ── Validation gate (v0.3.4) ──────────────
|
// ── Validation gate ──────────────
|
||||||
sc := ParseStageConfig(currentStage.StageConfig)
|
sc := ParseStageConfig(currentStage.StageConfig)
|
||||||
if sc.Validation != nil && sc.Validation.RequiredApprovals > 0 {
|
if sc.Validation != nil && sc.Validation.RequiredApprovals > 0 {
|
||||||
// Check for rejections first
|
// Check for rejections first
|
||||||
@@ -296,7 +296,7 @@ func (e *Engine) Cancel(ctx context.Context, instanceID string, userID string) e
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Public Entry (v0.3.3) ───────────────────
|
// ── Public Entry ───────────────────
|
||||||
|
|
||||||
// StartPublic creates an instance for a public_link workflow without authentication.
|
// StartPublic creates an instance for a public_link workflow without authentication.
|
||||||
// The caller is identified as "public:<uuid>". Returns the instance including its entry_token.
|
// The caller is identified as "public:<uuid>". Returns the instance including its entry_token.
|
||||||
@@ -358,7 +358,7 @@ func (e *Engine) AdvancePublic(ctx context.Context, entryToken string, stageData
|
|||||||
return e.advanceInternal(ctx, inst.ID, stageData, inst.StartedBy, 0)
|
return e.advanceInternal(ctx, inst.ID, stageData, inst.StartedBy, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Signoffs (v0.3.4) ─────────────────────────
|
// ── Signoffs ─────────────────────────
|
||||||
|
|
||||||
// SubmitSignoff records a user's approval or rejection at the current stage boundary.
|
// SubmitSignoff records a user's approval or rejection at the current stage boundary.
|
||||||
func (e *Engine) SubmitSignoff(ctx context.Context, instanceID, userID, decision, comment string) (*models.WorkflowSignoff, error) {
|
func (e *Engine) SubmitSignoff(ctx context.Context, instanceID, userID, decision, comment string) (*models.WorkflowSignoff, error) {
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ function createBrowserContext(overrides = {}) {
|
|||||||
function loadSource(sandbox, filename) {
|
function loadSource(sandbox, filename) {
|
||||||
const filepath = path.join(SRC, filename);
|
const filepath = path.join(SRC, filename);
|
||||||
if (!fs.existsSync(filepath)) {
|
if (!fs.existsSync(filepath)) {
|
||||||
throw new Error(`Source file not found: ${filename} (deleted in v0.37.10?)`);
|
throw new Error(`Source file not found: ${filename} (deleted?)`);
|
||||||
}
|
}
|
||||||
const code = fs.readFileSync(filepath, 'utf-8');
|
const code = fs.readFileSync(filepath, 'utf-8');
|
||||||
const ctx = vm.createContext(sandbox);
|
const ctx = vm.createContext(sandbox);
|
||||||
@@ -128,7 +128,6 @@ function readSourceSafe(filename) {
|
|||||||
/**
|
/**
|
||||||
* Extract the model-processing transform from fetchModels (app-state.js).
|
* Extract the model-processing transform from fetchModels (app-state.js).
|
||||||
* This is the core mapping logic that converts API response → App.models.
|
* This is the core mapping logic that converts API response → App.models.
|
||||||
* v0.22.8: unified persona naming — no more preset aliases.
|
|
||||||
*/
|
*/
|
||||||
function processModelsResponse(data, hiddenModels = new Set()) {
|
function processModelsResponse(data, hiddenModels = new Set()) {
|
||||||
return (data.data || data.models || []).map(m => {
|
return (data.data || data.models || []).map(m => {
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ describe('Team member dropdown population', () => {
|
|||||||
// ── Kernel surface template ──────────────────
|
// ── Kernel surface template ──────────────────
|
||||||
// old SPA scaffold is gone.
|
// old SPA scaffold is gone.
|
||||||
|
|
||||||
describe('Kernel surface templates (v0.1.0)', () => {
|
describe('Kernel surface templates', () => {
|
||||||
const templateSrc = readAllTemplates();
|
const templateSrc = readAllTemplates();
|
||||||
|
|
||||||
it('admin-mount div exists', () => {
|
it('admin-mount div exists', () => {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
// ==========================================
|
// ==========================================
|
||||||
// Debug Bootstrap (v0.37.18)
|
// Debug Bootstrap
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// Thin entry point: initializes the debug engine (synchronous,
|
// Thin entry point: initializes the debug engine (synchronous,
|
||||||
// captures early errors) then mounts the Preact debug modal
|
// captures early errors) then mounts the Preact debug modal
|
||||||
// after Preact globals are available.
|
// after Preact globals are available.
|
||||||
//
|
//
|
||||||
// Replaces the 673-line imperative debug.js from v0.37.14.
|
// Replaces the 673-line imperative debug.js.
|
||||||
// Engine: src/js/sw/components/debug/engine.js
|
// Engine: src/js/sw/components/debug/engine.js
|
||||||
// Modal: src/js/sw/components/debug/index.js
|
// Modal: src/js/sw/components/debug/index.js
|
||||||
|
|
||||||
|
|||||||
@@ -131,13 +131,13 @@ export function createDomains(restClient) {
|
|||||||
updateWorkflow: (id, wfId, data) => rc.put(`/api/v1/teams/${id}/workflows/${wfId}`, data),
|
updateWorkflow: (id, wfId, data) => rc.put(`/api/v1/teams/${id}/workflows/${wfId}`, data),
|
||||||
deleteWorkflow: (id, wfId) => rc.del(`/api/v1/teams/${id}/workflows/${wfId}`),
|
deleteWorkflow: (id, wfId) => rc.del(`/api/v1/teams/${id}/workflows/${wfId}`),
|
||||||
publishWorkflow: (id, wfId) => rc.post(`/api/v1/teams/${id}/workflows/${wfId}/publish`, {}),
|
publishWorkflow: (id, wfId) => rc.post(`/api/v1/teams/${id}/workflows/${wfId}/publish`, {}),
|
||||||
// Team workflow stages (v0.37.15 — FE wiring for existing BE routes)
|
// Team workflow stages
|
||||||
workflowStages: (id, wfId) => rc.get(`/api/v1/teams/${id}/workflows/${wfId}/stages`),
|
workflowStages: (id, wfId) => rc.get(`/api/v1/teams/${id}/workflows/${wfId}/stages`),
|
||||||
createWorkflowStage: (id, wfId, data) => rc.post(`/api/v1/teams/${id}/workflows/${wfId}/stages`, data),
|
createWorkflowStage: (id, wfId, data) => rc.post(`/api/v1/teams/${id}/workflows/${wfId}/stages`, data),
|
||||||
updateWorkflowStage: (id, wfId, sid, data) => rc.put(`/api/v1/teams/${id}/workflows/${wfId}/stages/${sid}`, data),
|
updateWorkflowStage: (id, wfId, sid, data) => rc.put(`/api/v1/teams/${id}/workflows/${wfId}/stages/${sid}`, data),
|
||||||
deleteWorkflowStage: (id, wfId, sid) => rc.del(`/api/v1/teams/${id}/workflows/${wfId}/stages/${sid}`),
|
deleteWorkflowStage: (id, wfId, sid) => rc.del(`/api/v1/teams/${id}/workflows/${wfId}/stages/${sid}`),
|
||||||
reorderWorkflowStages: (id, wfId, ids) => rc.patch(`/api/v1/teams/${id}/workflows/${wfId}/stages/reorder`, { ordered_ids: ids }),
|
reorderWorkflowStages: (id, wfId, ids) => rc.patch(`/api/v1/teams/${id}/workflows/${wfId}/stages/reorder`, { ordered_ids: ids }),
|
||||||
// Adopt global workflows (v0.3.6)
|
// Adopt global workflows
|
||||||
availableWorkflows: (id) => rc.get(`/api/v1/teams/${id}/workflows/available`),
|
availableWorkflows: (id) => rc.get(`/api/v1/teams/${id}/workflows/available`),
|
||||||
adoptWorkflow: (id, wfId) => rc.post(`/api/v1/teams/${id}/workflows/${wfId}/adopt`, {}),
|
adoptWorkflow: (id, wfId) => rc.post(`/api/v1/teams/${id}/workflows/${wfId}/adopt`, {}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ export function createCan(authRef) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the current user a platform admin?
|
* Is the current user a platform admin?
|
||||||
* v0.2.0: Uses RBAC grant instead of legacy role field.
|
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
function isAdmin() {
|
function isAdmin() {
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export async function boot() {
|
|||||||
sw.slots = slots;
|
sw.slots = slots;
|
||||||
sw.actions = actions;
|
sw.actions = actions;
|
||||||
|
|
||||||
// Realtime — room-scoped pub/sub over WebSocket (v0.5.0)
|
// Realtime — room-scoped pub/sub over WebSocket
|
||||||
sw.realtime = realtime;
|
sw.realtime = realtime;
|
||||||
|
|
||||||
// Shell helpers — imperative confirm/prompt backed by primitives
|
// Shell helpers — imperative confirm/prompt backed by primitives
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* SurfaceViewport — container where the active surface renders
|
* SurfaceViewport — container where the active surface renders
|
||||||
*
|
*
|
||||||
* Deliberately thin for v0.37.4. Error boundaries and
|
* Deliberately thin. Error boundaries and
|
||||||
* surface transitions will be added in later versions.
|
* surface transitions will be added in later versions.
|
||||||
*/
|
*/
|
||||||
const { html } = window;
|
const { html } = window;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Admin > Connections — global extension connection CRUD
|
* Admin > Connections — global extension connection CRUD
|
||||||
* v0.38.1: Scoped credential management for extensions
|
|
||||||
*/
|
*/
|
||||||
const { html } = window;
|
const { html } = window;
|
||||||
const { useState, useEffect, useCallback } = hooks;
|
const { useState, useEffect, useCallback } = hooks;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const ADMIN_LABELS = {
|
|||||||
audit: 'Audit',
|
audit: 'Audit',
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── v0.38.3: Extension config sections ──────
|
// ── Extension config sections ──────
|
||||||
// Packages declare config_section in their manifest targeting "admin".
|
// Packages declare config_section in their manifest targeting "admin".
|
||||||
// We merge them into the appropriate category and section module map.
|
// We merge them into the appropriate category and section module map.
|
||||||
const _configSections = window.__CONFIG_SECTIONS__ || [];
|
const _configSections = window.__CONFIG_SECTIONS__ || [];
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export default function PackagesSection() {
|
|||||||
} catch (e) { sw.toast(e.message, 'error'); }
|
} catch (e) { sw.toast(e.message, 'error'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Permissions drawer (v0.5.0) ────────────
|
// ── Permissions drawer ────────────
|
||||||
async function togglePerms(pkgId) {
|
async function togglePerms(pkgId) {
|
||||||
if (permsId === pkgId) { setPermsId(null); setPerms([]); return; }
|
if (permsId === pkgId) { setPermsId(null); setPerms([]); return; }
|
||||||
try {
|
try {
|
||||||
@@ -323,7 +323,7 @@ export default function PackagesSection() {
|
|||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
|
|
||||||
${/* ── Inline permissions drawer (v0.5.0) ── */``}
|
${/* ── Inline permissions drawer ── */``}
|
||||||
${permsId === pkg.id && html`
|
${permsId === pkg.id && html`
|
||||||
<div style="padding:8px 12px 12px 24px;background:var(--bg-2);border-bottom:1px solid var(--border);">
|
<div style="padding:8px 12px 12px 24px;background:var(--bg-2);border-bottom:1px solid var(--border);">
|
||||||
${perms.length === 0
|
${perms.length === 0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* DocsSurface — builtin documentation viewer (v0.6.2)
|
* DocsSurface — builtin documentation viewer
|
||||||
*
|
*
|
||||||
* Reads globals:
|
* Reads globals:
|
||||||
* __SECTION__ — active doc slug (e.g. "GETTING-STARTED")
|
* __SECTION__ — active doc slug (e.g. "GETTING-STARTED")
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
* Fetches markdown from GET /api/v1/docs/:name, renders with
|
* Fetches markdown from GET /api/v1/docs/:name, renders with
|
||||||
* a simple markdown-to-HTML converter. Sidebar lists all docs.
|
* a simple markdown-to-HTML converter. Sidebar lists all docs.
|
||||||
*
|
*
|
||||||
* v0.6.2: dark mode fix, topbar navigation, error handling.
|
|
||||||
*/
|
*/
|
||||||
const { html } = window;
|
const { html } = window;
|
||||||
const { useState, useEffect, useCallback, useMemo } = hooks;
|
const { useState, useEffect, useCallback, useMemo } = hooks;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* ConnectionsSection — personal extension connection CRUD
|
* ConnectionsSection — personal extension connection CRUD
|
||||||
* v0.38.1: Scoped credential management for extensions
|
|
||||||
*/
|
*/
|
||||||
const { html } = window;
|
const { html } = window;
|
||||||
const { useState, useEffect, useCallback } = hooks;
|
const { useState, useEffect, useCallback } = hooks;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const SECTION_TITLES = {
|
|||||||
connections: 'Connections', notifications: 'Notifications',
|
connections: 'Connections', notifications: 'Notifications',
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── v0.38.3: Extension config sections ──────
|
// ── Extension config sections ──────
|
||||||
// Packages declare config_section in their manifest. The backend passes
|
// Packages declare config_section in their manifest. The backend passes
|
||||||
// matching entries via __CONFIG_SECTIONS__. We merge them into the nav
|
// matching entries via __CONFIG_SECTIONS__. We merge them into the nav
|
||||||
// and section module map for lazy loading.
|
// and section module map for lazy loading.
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Team Admin > Connections — team-scoped extension connection CRUD
|
* Team Admin > Connections — team-scoped extension connection CRUD
|
||||||
* v0.38.1: Scoped credential management for extensions
|
|
||||||
*/
|
*/
|
||||||
const { html } = window;
|
const { html } = window;
|
||||||
const { useState, useEffect, useCallback } = hooks;
|
const { useState, useEffect, useCallback } = hooks;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const sectionModules = {
|
|||||||
activity: () => import('./activity.js'),
|
activity: () => import('./activity.js'),
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── v0.38.3: Extension config sections ──────
|
// ── Extension config sections ──────
|
||||||
const _configSections = window.__CONFIG_SECTIONS__ || [];
|
const _configSections = window.__CONFIG_SECTIONS__ || [];
|
||||||
const _base = window.__BASE__ || '';
|
const _base = window.__BASE__ || '';
|
||||||
for (const cs of _configSections) {
|
for (const cs of _configSections) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Team Admin > Members
|
* Team Admin > Members
|
||||||
* v0.3.4: custom team roles support
|
|
||||||
*/
|
*/
|
||||||
const { html } = window;
|
const { html } = window;
|
||||||
const { useState, useEffect, useCallback } = hooks;
|
const { useState, useEffect, useCallback } = hooks;
|
||||||
|
|||||||
@@ -663,7 +663,7 @@ function MonitorTab({ teamId }) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Signoff Panel (v0.3.4) ──────────────────
|
// ── Signoff Panel ──────────────────
|
||||||
|
|
||||||
function SignoffPanel({ instanceId, teamId }) {
|
function SignoffPanel({ instanceId, teamId }) {
|
||||||
const [signoffs, setSignoffs] = useState([]);
|
const [signoffs, setSignoffs] = useState([]);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
const CACHE_NAME = 'switchboard-%%APP_VERSION%%-%%BUILD_HASH%%';
|
const CACHE_NAME = 'switchboard-%%APP_VERSION%%-%%BUILD_HASH%%';
|
||||||
|
|
||||||
// App shell files to pre-cache on install (cleaned up in v0.37.12)
|
// App shell files to pre-cache on install (cleaned up)
|
||||||
const SHELL_FILES = [
|
const SHELL_FILES = [
|
||||||
'./',
|
'./',
|
||||||
'./index.html',
|
'./index.html',
|
||||||
@@ -35,7 +35,7 @@ const SHELL_FILES = [
|
|||||||
'./css/sw-chat-surface.css',
|
'./css/sw-chat-surface.css',
|
||||||
'./css/sw-notes-pane.css',
|
'./css/sw-notes-pane.css',
|
||||||
'./css/sw-notes-surface.css',
|
'./css/sw-notes-surface.css',
|
||||||
// JS — debug tooling (v0.37.18: repl.js absorbed into Preact debug components)
|
// JS — debug tooling
|
||||||
'./js/debug.js',
|
'./js/debug.js',
|
||||||
// Static assets
|
// Static assets
|
||||||
'./favicon.svg',
|
'./favicon.svg',
|
||||||
|
|||||||
Reference in New Issue
Block a user