Files
j3ssie 77bad65cd9 feat: v5.1.0 — orgs, npm distribution, bundled skills, platform vendoring
Org (tenant) layer
- New Org model with org_uuid denormalized onto workspaces, assets,
  vulnerabilities and runs so cross-workspace queries need no join
- Automatic attribution via BeforeAppendModel hooks; importers stay org-unaware
- Read semantics: empty org means no filter (backward compatible)
  Write semantics: empty org coerced to the default org
- Migration backfills every pre-existing row into the default org
- CLI: osmedeus org create/show/assign/use/rename/delete
- API: /osm/api/orgs CRUD plus ?org= on assets, vulns, runs and workspaces

npm distribution
- npm install -g @j3ssie/osmedeus ships the Go binary through npm
- One npm name with version-suffixed platform builds pulled in as aliased
  optionalDependencies, so an install downloads exactly one binary
- Binary ships gzipped and is decompressed on first run into a
  version-scoped cache, so an upgrade can never exec a stale binary
- make bump-version is the single source of truth for the version constant

Bundled agent skills
- public/skills/ embedded in the binary, installed via osmedeus skills install
- Filesystem-driven discovery: a new bundle needs no code change
- make sync-skills mirrors bundles out to the standalone skills repo

Platform sub-projects
- Vendor dashboard, registry and workflow under platform/ so they version
  with the engine they talk to; make sync-platform publishes them out
- Rebuild the embedded UI in public/ui/
2026-08-08 22:26:09 +08:00

70 lines
2.2 KiB
Go

package database
import (
"context"
"fmt"
"time"
)
// EnsureWorkspaceRuntime upserts a workspace and attributes it to an org.
//
// orgUUID is the org the caller explicitly selected (--org / $OSMEDEUS_ORG / the
// active org). An empty orgUUID means "no org was named", which is treated very
// differently on insert and update:
//
// - New workspace: it lands in the default org.
// - Existing workspace: its org is left alone. Re-scanning a workspace that
// `osmedeus org assign` put into an org must not silently drag it back to the
// default org just because this run did not pass --org.
//
// Naming an org explicitly does move the workspace, since that is the whole point
// of `osmedeus run --org`.
func EnsureWorkspaceRuntime(ctx context.Context, name, localPath, runWorkflow, stateExecutionLog, stateCompletedFile, stateWorkflowFile, stateWorkflowFolder, orgUUID string) error {
if db == nil {
return fmt.Errorf("database not connected")
}
if name == "" {
return fmt.Errorf("workspace name cannot be empty")
}
now := time.Now()
ws := &Workspace{
Name: name,
OrgUUID: NormalizeOrgUUID(orgUUID),
LocalPath: localPath,
DataSource: "local",
LastRun: &now,
RunWorkflow: runWorkflow,
StateExecutionLog: stateExecutionLog,
StateCompletedFile: stateCompletedFile,
StateWorkflowFile: stateWorkflowFile,
StateWorkflowFolder: stateWorkflowFolder,
CreatedAt: now,
UpdatedAt: now,
}
q := db.NewInsert().Model(ws).
On("CONFLICT (name) DO UPDATE").
Set("local_path = EXCLUDED.local_path").
Set("data_source = EXCLUDED.data_source").
Set("last_run = EXCLUDED.last_run").
Set("run_workflow = EXCLUDED.run_workflow").
Set("state_execution_log = EXCLUDED.state_execution_log").
Set("state_completed_file = EXCLUDED.state_completed_file").
Set("state_workflow_file = EXCLUDED.state_workflow_file").
Set("state_workflow_folder = EXCLUDED.state_workflow_folder").
Set("updated_at = EXCLUDED.updated_at")
if orgUUID != "" {
q = q.Set("org_uuid = EXCLUDED.org_uuid")
}
_, err := q.Exec(ctx)
if err == nil && orgUUID != "" {
invalidateWorkspaceOrgCache()
}
return err
}