diff --git a/.design/cleanup-candidates.md b/.design/cleanup-candidates.md index 4dc0c5f6f..219ffd488 100644 --- a/.design/cleanup-candidates.md +++ b/.design/cleanup-candidates.md @@ -76,7 +76,7 @@ The runtime broker's agent creation path builds an `api.StartOptions` struct and | `finalizeEnv` | `handlers.go:1932-2086` | Env-gather completion: env merge → hydrate template → git-clone → start | Each handler independently: -1. Resolves the hub-native grove path from `GroveSlug` (identical `~/.scion/groves/` block, duplicated at lines 323-350 and 1032-1054) +1. Resolves the hub-managed grove path from `GroveSlug` (identical `~/.scion/groves/` block, duplicated at lines 323-350 and 1032-1054) 2. Builds the merged env map with hub endpoint, broker name, debug flag, auth tokens 3. Translates `SCION_TELEMETRY_ENABLED` into `TelemetryOverride` 4. Hydrates templates from Hub @@ -108,7 +108,7 @@ func (s *Server) buildStartContext(ctx context.Context, r *http.Request, req Cre ``` This function encapsulates: -- Hub-native grove path resolution +- Hub-managed grove path resolution - Env merging (resolved env + config env + auth tokens + hub endpoint + broker identity + debug) - Template hydration - Git-clone env injection diff --git a/.design/dispatch-cleanup.md b/.design/dispatch-cleanup.md index 1d96f763e..889a01bcc 100644 --- a/.design/dispatch-cleanup.md +++ b/.design/dispatch-cleanup.md @@ -161,7 +161,7 @@ These four changes will materially improve robustness, reliability, and future d - values must be redacted in broker debug logs ### Why Local Settings Are Sometimes Ignored -In Hub-connected container flows, local grove settings may not represent the active Hub endpoint (for example, combo/hub-native routing or remote broker dispatch). In that mode, dispatch/broker authoritative inputs intentionally take precedence over stale local settings. +In Hub-connected container flows, local grove settings may not represent the active Hub endpoint (for example, combo/hub-managed routing or remote broker dispatch). In that mode, dispatch/broker authoritative inputs intentionally take precedence over stale local settings. ## Troubleshooting Matrix diff --git a/.design/git-grove-duplicates.md b/.design/git-grove-duplicates.md index 2d94ebb3a..047c79a86 100644 --- a/.design/git-grove-duplicates.md +++ b/.design/git-grove-duplicates.md @@ -38,7 +38,7 @@ Each grove has its own set of agents, templates, and settings. Agents in one gro - Merging or synchronizing work between groves sharing the same repository. - Cross-grove agent visibility or communication. -- Changing the hub-native (non-git) grove model. +- Changing the hub-managed (non-git) grove model. --- @@ -87,7 +87,7 @@ Since SQLite doesn't support `ALTER TABLE ... DROP CONSTRAINT`, the migration wi **New behavior:** Grove IDs are always randomly generated UUIDs (`uuid.New()`), regardless of whether the grove has a git remote. -**Rationale:** Deterministic IDs were designed to enforce the 1:1 mapping. With multiple groves per URL, deterministic IDs would cause collisions. Random UUIDs are already used for hub-native groves and work correctly. +**Rationale:** Deterministic IDs were designed to enforce the 1:1 mapping. With multiple groves per URL, deterministic IDs would cause collisions. Random UUIDs are already used for hub-managed groves and work correctly. **`HashGroveID()` is retained** but repurposed: it is no longer used for grove ID generation. It may still be useful for other deterministic identifiers (e.g., cache keys), so it is not removed. Callers that used it for grove ID generation are updated. @@ -96,7 +96,7 @@ Since SQLite doesn't support `ALTER TABLE ... DROP CONSTRAINT`, the migration wi ### 3.3 Enforce Unique Slugs with Serial Numbering Slugs must remain unique because they are used in: -- Filesystem paths (`~/.scion/groves//` for hub-native, `~/.scion/grove-configs/__/` for external) +- Filesystem paths (`~/.scion/groves//` for hub-managed, `~/.scion/grove-configs/__/` for external) - API routing (`/api/v1/groves//...`) - CLI references (`scion start agent --grove `) @@ -167,7 +167,7 @@ All three workspace strategies (represented as three modes at the model layer) c - **Per-agent clone** (`GitClone` set, `SharedWorkspace=false`): Each agent in a git grove clones the repository independently inside its container. This is the default for git groves. - **Shared workspace clone** (`GitClone` unset, `SharedWorkspace=true`, `Workspace` set): A single shared git clone is mounted by all agents in the grove, rather than each agent cloning independently. -- **Hub-native workspace** (`GitClone` unset, `SharedWorkspace=false`, `Workspace` set): Non-git groves with a hub-managed filesystem. +- **Hub-managed workspace** (`GitClone` unset, `SharedWorkspace=false`, `Workspace` set): Non-git groves with a hub-managed filesystem. The workspace strategy is determined by grove configuration (specifically the `scion.dev/workspace-mode` label), not by the number of groves sharing a URL. Multiple groves sharing the same git remote may each independently choose their own workspace strategy. @@ -272,7 +272,7 @@ GetInstallationForRepository(ctx context.Context, repoFullName string) (*GitHubI **Cons:** - The sequence number itself needs coordination (what if two clients try to create grove #2 simultaneously?). - Deterministic creation was valuable precisely because it avoided coordination — adding a sequence number undermines the benefit. -- Random UUIDs are simpler and already proven (hub-native groves use them). +- Random UUIDs are simpler and already proven (hub-managed groves use them). **Decision:** Rejected. Random UUIDs are simpler and sufficient. diff --git a/.design/git-workspace-hybrid.md b/.design/git-workspace-hybrid.md index 3b888569c..a975c6726 100644 --- a/.design/git-workspace-hybrid.md +++ b/.design/git-workspace-hybrid.md @@ -15,7 +15,7 @@ Today, Hub-created groves come in two flavors: | Type | Workspace Strategy | Agent Isolation | |------|-------------------|-----------------| | **Git-based** (`gitRemote` set) | Each agent clones the repo independently into its container. Workspace is ephemeral — lost on container deletion. | Full isolation: each agent has its own `.git`, branch, and working tree. | -| **Hub-native** (no `gitRemote`) | A single shared workspace at `~/.scion/groves//` is mounted into all agents. | No isolation: agents share the same files. Concurrent writes can conflict. | +| **Hub-managed** (no `gitRemote`) | A single shared workspace at `~/.scion/groves//` is mounted into all agents. | No isolation: agents share the same files. Concurrent writes can conflict. | Both models have significant limitations for a common use case: **teams that want a git-backed project but prefer a shared, persistent workspace** on the Hub rather than ephemeral per-agent clones. @@ -26,7 +26,7 @@ Both models have significant limitations for a common use case: **teams that wan 3. **No shared state** — agents cannot see each other's file changes unless they push to the remote and pull. 4. **Clone latency** — each agent start incurs clone time, especially for large repos. -**Pain points with the current hub-native model:** +**Pain points with the current hub-managed model:** 1. **No git integration** — no ability to commit, push, create branches, or open PRs. 2. **No source of truth** — the workspace is a bare directory with no version history. @@ -34,7 +34,7 @@ Both models have significant limitations for a common use case: **teams that wan ### 1.2 Proposal -Introduce a **git-workspace hybrid** grove mode: a git grove that provisions a **single shared git clone** into the hub-native workspace path instead of per-agent clones. Agents mount this shared workspace, just as hub-native groves work today. +Introduce a **git-workspace hybrid** grove mode: a git grove that provisions a **single shared git clone** into the hub-managed workspace path instead of per-agent clones. Agents mount this shared workspace, just as hub-managed groves work today. This is offered as a **sub-mode of git-based groves** — the existing per-agent clone behavior is retained as the default. Users choose the workspace mode when creating a git grove. @@ -49,7 +49,7 @@ This combines the benefits of both models: 1. Allow creating a git grove that provisions a shared, persistent workspace. 2. The workspace is a real git clone — agents can commit, branch, push, and pull. 3. Only one clone operation per grove (not per agent). -4. Agents share the workspace filesystem, similar to hub-native groves. +4. Agents share the workspace filesystem, similar to hub-managed groves. 5. Support private repositories via `GITHUB_TOKEN` or GitHub App credentials. ### 1.4 Non-Goals @@ -85,13 +85,13 @@ This combines the benefits of both models: - Agents still can't see each other's uncommitted changes (separate working trees). - Worktree creation adds startup latency (though less than a full clone). - More complex lifecycle management (worktree cleanup, branch tracking). -- Doesn't match the hub-native "shared workspace" mental model that users expect. +- Doesn't match the hub-managed "shared workspace" mental model that users expect. **Verdict:** This is a valid future extension that would leverage the existing local-mode worktree patterns, but it doesn't fulfill the "shared workspace" goal for this scope. It's essentially an optimization of the current git-based model. Marked as a potential future option that could be offered alongside the shared workspace mode. ### 2.2 Alternative B: Clone Once, Mount Shared (Proposed) -**Concept:** Clone the repo once into the hub-native workspace path. All agents mount the same directory. The workspace is a normal git working tree (not bare). +**Concept:** Clone the repo once into the hub-managed workspace path. All agents mount the same directory. The workspace is a normal git working tree (not bare). ``` ~/.scion/groves// @@ -106,14 +106,14 @@ This combines the benefits of both models: - Agents can see each other's file changes in real-time. - Full git operations available (commit, push, pull, branch). - Minimal lifecycle complexity — it's just a directory. -- Matches hub-native behavior exactly (just with git pre-initialized). +- Matches hub-managed behavior exactly (just with git pre-initialized). **Cons:** - No isolation: concurrent agent writes can conflict. - Single branch at a time (or agents must coordinate git operations). - Potential for `.git` lock contention with concurrent git operations. -**Verdict:** This is the simplest approach and matches the user's stated desire for a "shared workspace with git". The lack of isolation is a known trade-off that users accept when choosing hub-native groves today. +**Verdict:** This is the simplest approach and matches the user's stated desire for a "shared workspace with git". The lack of isolation is a known trade-off that users accept when choosing hub-managed groves today. ### 2.3 Alternative C: Git Clone + GCS Sync Hybrid @@ -150,7 +150,7 @@ This combines the benefits of both models: **Alternative B (Clone Once, Mount Shared)** is the proposed approach, offered as a sub-mode of git groves alongside the existing per-agent clone mode (Alternative D). It provides the best balance of simplicity, git integration, and shared workspace behavior. The isolation trade-offs are acceptable because: 1. Users choosing "shared workspace" mode are explicitly opting into shared state. -2. The same trade-off already exists for hub-native groves. +2. The same trade-off already exists for hub-managed groves. 3. Per-agent isolation can be layered on later (Alternative A) as a separate grove option. --- @@ -245,12 +245,12 @@ This is simpler than maintaining a grove status state machine for provisioning ### 3.3 Agent Workspace Mounting -Once the shared workspace is cloned, agents mount it identically to hub-native groves: +Once the shared workspace is cloned, agents mount it identically to hub-managed groves: ```go // In agent provisioning (pkg/agent/provision.go): if grove.IsSharedWorkspace() { - // Mount the shared workspace — same as hub-native + // Mount the shared workspace — same as hub-managed workspaceSource = hubNativeGrovePath(grove.Slug) // Skip worktree creation — workspace already exists shouldCreateWorktree = false @@ -306,7 +306,7 @@ The existing `POST /api/v1/groves` handler needs to: 1. Accept a new `workspaceMode` field (or detect it from labels). 2. When `workspaceMode: "shared"` and `gitRemote` is set: - a. Create the hub-native workspace directory (`~/.scion/groves//`). + a. Create the hub-managed workspace directory (`~/.scion/groves//`). b. Perform the host-side git clone into the workspace directory. c. On clone success: create the grove record. d. On clone failure: clean up the workspace directory and return an error. @@ -337,7 +337,7 @@ When an agent is started on a shared-workspace git grove: 1. **Skip git clone** — the workspace already contains a clone. 2. **Skip worktree creation** — agents share the workspace directly. -3. **Mount shared workspace** — same bind mount as hub-native groves. +3. **Mount shared workspace** — same bind mount as hub-managed groves. 4. **Configure credential helper** — per-agent `$HOME/.gitconfig` with `GITHUB_TOKEN`. 5. **Branch field behavior** — the agent creation form preserves the branch name field. For shared-workspace groves, the default value is the workspace's current branch (instead of an agent-named branch as used for clone-based agents). Agents can change branches but must coordinate since only one branch can be checked out at a time. @@ -358,7 +358,7 @@ case isGit && noExplicitWorkspace: // (existing behavior) default: - // Hub-native: mount shared workspace + // Hub-managed: mount shared workspace // (existing behavior) } ``` @@ -369,7 +369,7 @@ The existing `handleGroveWorkspace` handler (`grove_workspace_handlers.go:84-86` ```go if grove.GitRemote != "" { - Conflict(w, "Workspace file management is only available for hub-native groves") + Conflict(w, "Workspace file management is only available for hub-managed groves") return } ``` @@ -378,7 +378,7 @@ For shared-workspace groves, this check should be relaxed: ```go if grove.GitRemote != "" && !grove.IsSharedWorkspace() { - Conflict(w, "Workspace file management is only available for hub-native and git-shared groves") + Conflict(w, "Workspace file management is only available for hub-managed and git-shared groves") return } ``` @@ -396,7 +396,7 @@ With a shared workspace, two agents running concurrently could: - Run `git checkout` to different branches - Run conflicting git operations (e.g., concurrent commits) -**Resolution:** No formal coordination mechanism. This is up to the grove owner and users to manage — the same as hub-native groves today. Document the shared workspace semantics clearly and let users decide coordination strategies via agent instructions. +**Resolution:** No formal coordination mechanism. This is up to the grove owner and users to manage — the same as hub-managed groves today. Document the shared workspace semantics clearly and let users decide coordination strategies via agent instructions. ### 4.2 Branch Management @@ -442,7 +442,7 @@ No credential files are stored in the workspace (see Section 3.4), so there is n The grove creation flow uses a two-step type selection: -1. **Primary type choice:** `Hub Workspace` or `Git Repository` +1. **Primary type choice:** `Hub-managed Workspace` or `Git Repository` 2. **Git mode choice (shown when Git Repository is selected):** - `Per-agent clone` (default) — each agent gets its own clone (existing behavior). - `Shared workspace` — a single shared clone mounted by all agents (new). @@ -457,7 +457,7 @@ type GitWorkspaceMode = 'per-agent' | 'shared'; ```html -Hub Workspace +Hub-managed Workspace Git Repository @@ -477,7 +477,7 @@ When `shared` is selected: For shared-workspace git groves, the grove detail page should show: - Git remote URL (linked to GitHub). - Current branch checked out. -- File browser (reuse hub-native workspace file listing). +- File browser (reuse hub-managed workspace file listing). - A "Pull Latest" action button. ### 5.3 Agent Creation Form @@ -517,7 +517,7 @@ The branch name field on the new-agent form adapts its default based on workspac - ✅ Hub dispatcher resolves `grove.IsSharedWorkspace()` and propagates to broker. - ✅ Broker injects `SCION_SHARED_WORKSPACE` env var for sciontool. 2. ✅ Mount shared workspace path for agents on shared-workspace groves. - - ✅ Hub sets workspace to hub-native grove path; broker passes through. + - ✅ Hub sets workspace to hub-managed grove path; broker passes through. - ✅ ProvisionAgent takes explicit workspace path (skips worktree creation). 3. ✅ Configure per-agent credential helper in `$HOME/.gitconfig` with `GITHUB_TOKEN`. - ✅ ProvisionAgent writes credential helper to agent home `.gitconfig`. @@ -525,7 +525,7 @@ The branch name field on the new-agent form adapts its default based on workspac 4. ✅ Update default branch name logic for new-agent form. - ✅ Shared-workspace agents default to grove's `scion.dev/default-branch` label (or "main"). 5. ✅ Test concurrent agent access to shared workspace. - - ✅ Documented as user responsibility (same as hub-native groves). + - ✅ Documented as user responsibility (same as hub-managed groves). ### Phase 4: Web UI ✅ Completed @@ -535,7 +535,7 @@ The branch name field on the new-agent form adapts its default based on workspac - ✅ `workspaceMode` and `scion.dev/workspace-mode` label sent in API request. 2. ✅ Enable workspace file browser for shared-workspace groves. - ✅ `shouldShowFilesSection()` and `getFileTabs()` updated to include workspace tab for shared-workspace groves. - - ✅ File loading on page load triggers for shared-workspace groves (same as hub-native). + - ✅ File loading on page load triggers for shared-workspace groves (same as hub-managed). 3. ✅ Add "Pull Latest" action to grove detail page. - ✅ Backend `POST /api/v1/groves/{id}/workspace/pull` endpoint with `git pull --ff-only`. - ✅ `PullSharedWorkspace` utility function in `pkg/util/git.go` with token auth and credential sanitization. @@ -580,7 +580,7 @@ The branch name field on the new-agent form adapts its default based on workspac | # | Question | Status | Decision | |---|----------|--------|----------| -| 1 | **Workspace model?** | Resolved | Shared workspace (Alternative B). Simplest model, matches hub-native mental model. | +| 1 | **Workspace model?** | Resolved | Shared workspace (Alternative B). Simplest model, matches hub-managed mental model. | | 2 | **Retain per-agent clone?** | Resolved | Yes. Per-agent clone remains the default. Shared workspace is a sub-mode choice. | | 3 | **Type modeling?** | Resolved | Sub-type of git grove via `scion.dev/workspace-mode` label. GroveType remains `"git"`. See Section 3.1 for rationale. | | 4 | **Clone mechanism?** | Resolved | Host-side clone by Hub/broker. Simpler, faster, better error handling than bootstrap container. | @@ -612,7 +612,7 @@ The branch name field on the new-agent form adapts its default based on workspac | File | Relevance | |------|-----------| -| `pkg/hub/handlers.go` | Grove creation handler. Hub-native workspace init. | +| `pkg/hub/handlers.go` | Grove creation handler. Hub-managed workspace init. | | `pkg/hub/grove_workspace_handlers.go` | Workspace file management (currently rejects git groves). | | `pkg/agent/provision.go` | Agent provisioning. Workspace resolution and worktree creation. | | `pkg/runtime/common.go` | Container workspace mounting logic. | diff --git a/.design/grove-dirs.md b/.design/grove-dirs.md index f284884e8..5b74e918c 100644 --- a/.design/grove-dirs.md +++ b/.design/grove-dirs.md @@ -83,7 +83,7 @@ This location is: - Per-grove (naturally scoped) - Persistent across agent restarts and reprovisioning -For hub-native groves, shared dirs live at `~/.scion/grove-configs//shared-dirs//` on each broker — the same grove-configs path used for agent homes. The `~/.scion/groves//` path is reserved for hub-native workspaces, not configuration state. +For hub-managed groves, shared dirs live at `~/.scion/grove-configs//shared-dirs//` on each broker — the same grove-configs path used for agent homes. The `~/.scion/groves//` path is reserved for hub-managed workspaces, not configuration state. ## Mount Target Strategy diff --git a/.design/grove-level-templates.md b/.design/grove-level-templates.md index c481246f7..bc1942ac5 100644 --- a/.design/grove-level-templates.md +++ b/.design/grove-level-templates.md @@ -97,13 +97,13 @@ After evaluating several approaches (see [Appendix: Rejected Approaches](#append 5. The agent creation form is **populated by available grove templates** **Why this works universally**: -- The agent container has the grove's filesystem mounted (whether linked, git-cloned, or hub-native) +- The agent container has the grove's filesystem mounted (whether linked, git-cloned, or hub-managed) - The `scion` CLI inside the container has access to all local templates - No special git access or broker APIs needed from the Hub **Container choice**: The dummy agent uses the `scion-base` container image (which includes the `scion` CLI) with the generic harness setting. It only needs Hub auth and endpoint environment variables — not a full LLM harness. The agent should be immediately deleted after the sync completes. -**Hub-native groves**: These are the simplest case — templates are managed entirely on the Hub. No container-based sync is needed. Future improvements may add direct template creation/editing in the web UI, but for now hub-native grove templates are also managed via CLI and synced. +**Hub-managed groves**: These are the simplest case — templates are managed entirely on the Hub. No container-based sync is needed. Future improvements may add direct template creation/editing in the web UI, but for now hub-managed grove templates are also managed via CLI and synced. --- @@ -133,7 +133,7 @@ After evaluating several approaches (see [Appendix: Rejected Approaches](#append **Resolution**: The in-container sync approach (Section 3.2) works here — after cloning the repo, the dummy agent has access to the in-repo templates, and `scion templates sync --all` uploads everything it finds. -### 4.3 Hub-Native Groves +### 4.3 Hub-Managed Groves **Simplest case**: Templates are managed entirely on the Hub. The web UI creates/edits templates at grove scope directly. No filesystem variance to reconcile, no need for the in-container sync flow. @@ -226,8 +226,8 @@ The web UI needs to: - Display read-only template list in grove settings - Populate agent creation form with Hub-synced grove-scoped templates only -### Phase 3: Future — Hub-Native Template Editing (deferred) -- Web UI support for creating/editing grove templates directly (hub-native groves first) +### Phase 3: Future — Hub-Managed Template Editing (deferred) +- Web UI support for creating/editing grove templates directly (hub-managed groves first) - Template content editor with file management - No container needed — direct Hub storage operations @@ -274,6 +274,6 @@ Combined broker inventory reporting with lazy on-demand sync. Not selected in fa - [Hosted Templates](hosted/hosted-templates.md) - Hub template storage and management - [Decoupling Templates](decouple-templates.md) - Template/harness separation analysis - [Git Groves](hosted/git-groves.md) - Git grove architecture -- [Hub Groves](hosted/hub-groves.md) - Hub-native grove design +- [Hub Groves](hosted/hub-groves.md) - Hub-managed grove design - [Settings Externalization](../commit d0507b1) - Recent change moving git grove config external (template portion to be reverted) - [Agnostic Template Design](agnostic-template-design.md) - Harness-agnostic template system diff --git a/.design/grove-mount-protection.md b/.design/grove-mount-protection.md index 774f04a88..bde5452ad 100644 --- a/.design/grove-mount-protection.md +++ b/.design/grove-mount-protection.md @@ -58,10 +58,10 @@ All `.scion` configuration folders (other than the special global `~/.scion/`) a ``` ~/.scion/grove-configs/__/.scion/ # All grove configs -~/.scion/groves// # Hub-native workspaces only (pure workspace holder) +~/.scion/groves// # Hub-managed workspaces only (pure workspace holder) ``` -This separation means `~/.scion/groves/` is a "pure" workspaces directory, and all configs are isolated from workspace content — whether those workspaces are hub-native (created via hub→broker) or linked by a user from the broker filesystem to a hub. +This separation means `~/.scion/groves/` is a "pure" workspaces directory, and all configs are isolated from workspace content — whether those workspaces are hub-managed (created via hub→broker) or linked by a user from the broker filesystem to a hub. ### Grove Path Naming @@ -270,7 +270,7 @@ Audited all in-container CLI operations to verify they route through the Hub API | Worktree compatibility | N/A | Preserved (worktrees stay in-repo) | | Code complexity | Medium (~10 resolution sites) | Medium (provisioning + cleanup) | | Template committability | Templates in external config | Templates in `.scion/templates/` (in git) | -| Hub model convergence | Good (unifies with hub-native) | Partial | +| Hub model convergence | Good (unifies with hub-managed) | Partial | | Defense model | Structural (data not present) | Structural (data not present) | --- diff --git a/.design/hosted/grove-settings.md b/.design/hosted/grove-settings.md index e4ab1d639..a63139397 100644 --- a/.design/hosted/grove-settings.md +++ b/.design/hosted/grove-settings.md @@ -159,7 +159,7 @@ This ensures a single source of truth per grove and avoids any divergence betwee For **linked groves**, grove settings live on the broker's filesystem (`.scion/settings.yaml`). The hub stores the values and syncs them to the broker, which owns the file on disk. -For **hub-native groves**, settings live in `~/.scion/groves//.scion/settings.yaml` on each provider broker. The hub writes these directly during grove provisioning. +For **hub-managed groves**, settings live in `~/.scion/groves//.scion/settings.yaml` on each provider broker. The hub writes these directly during grove provisioning. ## UI Design diff --git a/.design/hosted/hub-groves.md b/.design/hosted/hub-groves.md index 623c1a564..1aa900203 100644 --- a/.design/hosted/hub-groves.md +++ b/.design/hosted/hub-groves.md @@ -1,4 +1,4 @@ -# Hub-Native Groves: Filesystem-Based Workspaces on the Hub +# Hub-Managed Groves: Filesystem-Based Workspaces on the Hub **Created:** 2026-02-23 **Status:** Partially-implemented @@ -13,7 +13,7 @@ Today, groves are created in two ways: 1. **Local-first**: Run `scion grove init` inside a local git checkout, optionally register with the Hub. 2. **Git-URL-first**: Create a grove on the Hub from a remote git URL (per `git-groves.md`). Agents clone the repo at startup. -This document proposes a third mode: **Hub-native groves** — groves whose workspace is a plain directory on the Hub server's filesystem, with no backing git repository. These groves are created entirely through the web interface (or Hub API), enabling users to spin up agent workspaces without any local machine, CLI, or git hosting involvement. +This document proposes a third mode: **Hub-managed groves** — groves whose workspace is a plain directory on the Hub server's filesystem, with no backing git repository. These groves are created entirely through the web interface (or Hub API), enabling users to spin up agent workspaces without any local machine, CLI, or git hosting involvement. ### Motivation @@ -39,7 +39,7 @@ This document proposes a third mode: **Hub-native groves** — groves whose work ## 2. Filesystem Layout -Hub-native groves are stored under the global Scion directory on the Hub server: +Hub-managed groves are stored under the global Scion directory on the Hub server: ``` ~/.scion/groves/ @@ -68,7 +68,7 @@ The grove directory name is derived from the grove slug. Since slugs are already ### 3.1 Conceptual Steps -Creating a hub-native grove is equivalent to: +Creating a hub-managed grove is equivalent to: ```bash mkdir -p ~/.scion/groves/ @@ -108,13 +108,13 @@ The Hub server imports `pkg/config` and calls `InitProject(targetDir, harnesses) **Rejected alternatives:** - **Hub shells out to `scion` CLI** — Subprocess error handling is less ergonomic, and interactive prompts would need to be bypassed. Not worth the indirection. -- **Dedicated minimal handler** — Risk of parallel code paths diverging from local behavior. Could be a later evolution if hub-native groves develop distinct requirements. +- **Dedicated minimal handler** — Risk of parallel code paths diverging from local behavior. Could be a later evolution if hub-managed groves develop distinct requirements. --- ## 4. Agent Workspace Provisioning -When an agent is created against a hub-native grove, the workspace must be made available to the agent container. Hub-native groves should be treated the same as any local non-git grove — matching existing solo-mode behavior. +When an agent is created against a hub-managed grove, the workspace must be made available to the agent container. Hub-managed groves should be treated the same as any local non-git grove — matching existing solo-mode behavior. ### 4.1 Colocated Broker: Direct Bind-Mount @@ -146,41 +146,41 @@ On the remote broker, the workspace is created at the same conventional path (`~ **Decision: Infer from absence of GitRemote (no schema change).** -If `GitRemote == ""`, the grove is hub-native. The workspace path is derived conventionally from `~/.scion/groves/`. No new fields needed. +If `GitRemote == ""`, the grove is hub-managed. The workspace path is derived conventionally from `~/.scion/groves/`. No new fields needed. -Hub-native groves should be treated as much as possible like any local non-git grove on any broker, except at a conventional path location (`~/.scion/groves/`) instead of an arbitrary pre-existing path that gets stored when a broker is linked. +Hub-managed groves should be treated as much as possible like any local non-git grove on any broker, except at a conventional path location (`~/.scion/groves/`) instead of an arbitrary pre-existing path that gets stored when a broker is linked. Labels can be added for metadata if needed without a migration. -### 5.2 Grove ID for Hub-Native Groves +### 5.2 Grove ID for Hub-Managed Groves -Git-anchored groves use a deterministic hash of the normalized git URL. Hub-native groves have no URL to hash, so they should use a generated UUID — which is already the fallback in `GenerateGroveID()` when no git remote is found. +Git-anchored groves use a deterministic hash of the normalized git URL. Hub-managed groves have no URL to hash, so they should use a generated UUID — which is already the fallback in `GenerateGroveID()` when no git remote is found. ### 5.3 populateAgentConfig Changes -The existing `populateAgentConfig()` in `pkg/hub/handlers.go:4397` populates `GitClone` config when `grove.GitRemote != ""`. For hub-native groves, it should instead set the `Workspace` field to the grove's filesystem path (for colocated brokers) or set `WorkspaceStoragePath` (for remote brokers after sync upload). +The existing `populateAgentConfig()` in `pkg/hub/handlers.go:4397` populates `GitClone` config when `grove.GitRemote != ""`. For hub-managed groves, it should instead set the `Workspace` field to the grove's filesystem path (for colocated brokers) or set `WorkspaceStoragePath` (for remote brokers after sync upload). --- ## 6. Resolved Questions -### Q1: Should hub-native groves auto-initialize a git repo? +### Q1: Should hub-managed groves auto-initialize a git repo? -**Decision: No, not for now.** Hub-native groves should mirror the behavior of other non-git workspaces. Auto-initializing git is a potential future improvement, possibly as an optional argument at creation time. +**Decision: No, not for now.** Hub-managed groves should mirror the behavior of other non-git workspaces. Auto-initializing git is a potential future improvement, possibly as an optional argument at creation time. ### Q2: How should the Hub server discover its own URL for `hub enable`? **Decision: Read from `~/.scion/settings.yaml`.** The Hub server's own endpoint URL can be retrieved from the global settings file, which is already configured when the Hub is set up. -### Q3: What happens when a hub-native grove is deleted? +### Q3: What happens when a hub-managed grove is deleted? **Decision: Full cleanup.** The grove record is deleted from the database, and the filesystem directory at `~/.scion/groves//` is removed. When `deleteAgents=true` is passed, agent deletions are dispatched to their runtime brokers before the grove record is deleted (so containers are stopped and agent files are cleaned up). Database cascade handles removal of agent, template, and provider records. -### Q4: Should hub-native groves be promotable to git-remote groves? +### Q4: Should hub-managed groves be promotable to git-remote groves? -**Decision: Deferred.** This is a strong candidate for a future improvement — converting a hub-native grove to a git-anchored grove by initializing git, adding a remote, pushing, and updating the grove record. Not in initial scope. +**Decision: Deferred.** This is a strong candidate for a future improvement — converting a hub-managed grove to a git-anchored grove by initializing git, adding a remote, pushing, and updating the grove record. Not in initial scope. -### Q5: Can multiple Hubs serve the same hub-native grove? +### Q5: Can multiple Hubs serve the same hub-managed grove? **Decision: No.** The grove lives on one Hub's filesystem. This is a single-Hub feature by nature. Remote brokers can serve agents for it via sync, but the source of truth is one Hub's disk. @@ -190,7 +190,7 @@ The existing `populateAgentConfig()` in `pkg/hub/handlers.go:4397` populates `Gi ### Q7: Workspace seeding / initial content -**Decision: Start empty.** Hub-native groves are created with an empty workspace. Content can be added via the existing CLI sync mechanism. File upload and starter templates are deferred to a later phase. +**Decision: Start empty.** Hub-managed groves are created with an empty workspace. Content can be added via the existing CLI sync mechanism. File upload and starter templates are deferred to a later phase. --- @@ -201,9 +201,9 @@ The existing `populateAgentConfig()` in `pkg/hub/handlers.go:4397` populates `Gi The grove creation form should include a **distinct mode selector** for choosing the grove type: - **Git Repository** — Existing flow: user provides a git URL. -- **Hub Workspace** — New flow: no git URL, creates a hub-native grove. +- **Hub-managed Workspace** — New flow: no git URL, creates a hub-managed grove. -The Hub Workspace form should allow the user to specify: +The Hub-managed Workspace form should allow the user to specify: - Grove name (required). - Slug (auto-derived, optionally overridden). - Visibility (private/team/public). @@ -212,13 +212,13 @@ The Hub Workspace form should allow the user to specify: Grove type should be visually distinguishable via a **small glyph on the grove card component**: - **Git-based groves**: branching icon with mouseover helptext "Git based grove". -- **Hub-native groves**: folder icon with mouseover helptext "Folder based grove". +- **Hub-managed groves**: folder icon with mouseover helptext "Folder based grove". -The grove detail view should omit the git remote / clone URL section for hub-native groves. +The grove detail view should omit the git remote / clone URL section for hub-managed groves. ### 7.3 File Browser -A web-based file browser for hub-native grove workspaces is a natural follow-on feature but is **out of scope** for this design. Noted as a motivating future use case. +A web-based file browser for hub-managed grove workspaces is a natural follow-on feature but is **out of scope** for this design. Noted as a motivating future use case. --- @@ -245,14 +245,14 @@ When bind-mounting grove directories into agent containers, standard container i ## 9. Implementation Phases -### Phase 1: Minimal Hub-Native Grove +### Phase 1: Minimal Hub-Managed Grove completed - Hub API accepts grove creation with no `gitRemote`. - Hub creates `~/.scion/groves//` directory with `InitProject()`. - Writes hub settings into `.scion/settings.yaml` (hub endpoint read from global settings). - Colocated broker bind-mounts the grove directory for agents (shared mount, matching solo-mode). -- Web UI grove creation form with distinct mode selector for git vs hub workspace. +- Web UI grove creation form with distinct mode selector for git vs hub-managed workspace. - Grove card glyph distinguishing git-based and folder-based groves. ### Phase 2: Remote Broker Support @@ -266,20 +266,20 @@ completed ### Phase 3: Workspace Content Seeding completed -- Web UI allows uploading initial files into a hub-native grove. +- Web UI allows uploading initial files into a hub-managed grove. - Hub API endpoint for uploading files to a grove's workspace. - Optional starter templates for common project types. ### Phase 4: Grove Promotion pending -- Convert a hub-native grove to a git-anchored grove. +- Convert a hub-managed grove to a git-anchored grove. - Initialize git, add remote, push existing content. - Update grove record with `GitRemote`. ### Future Improvements -- **Optional git init at creation**: Allow an optional `--git` argument when creating hub-native groves to auto-initialize a git repository. +- **Optional git init at creation**: Allow an optional `--git` argument when creating hub-managed groves to auto-initialize a git repository. - ~~**Filesystem purge/prune**: Hub admin/maintenance tooling to clean up filesystem directories for soft-deleted groves.~~ (Implemented: grove deletion now removes filesystem directories.) - **Storage quotas**: Per-user and per-grove disk usage limits for multi-tenant deployments. - **Web file browser**: Browse and edit grove workspace files through the web UI. @@ -290,7 +290,7 @@ pending | Design | Relationship | |--------|-------------| -| `git-groves.md` | Complementary — git groves use clone, hub-native groves use local filesystem. Same grove API, different workspace strategy. | -| `sync-design.md` | Hub-native groves use workspace sync for remote broker support (Phase 2). | -| `hosted-architecture.md` | Hub-native groves fit the grove-centric model. The Hub is both state server and workspace host. | -| `secrets.md` | Hub-native groves use the same secret management. No git tokens needed, but API keys and other secrets still apply. | +| `git-groves.md` | Complementary — git groves use clone, hub-managed groves use local filesystem. Same grove API, different workspace strategy. | +| `sync-design.md` | Hub-managed groves use workspace sync for remote broker support (Phase 2). | +| `hosted-architecture.md` | Hub-managed groves fit the grove-centric model. The Hub is both state server and workspace host. | +| `secrets.md` | Hub-managed groves use the same secret management. No git tokens needed, but API keys and other secrets still apply. | diff --git a/.design/hosted/logging-components.md b/.design/hosted/logging-components.md index 57b731ba5..341849a90 100644 --- a/.design/hosted/logging-components.md +++ b/.design/hosted/logging-components.md @@ -85,7 +85,7 @@ Useful for debugging specific operational areas; lower log volume or less freque | `broker.heartbeat` | `pkg/runtimebroker/heartbeat.go` | Periodic status reports to hub. Very noisy at DEBUG level — filtering by subsystem prevents it from overwhelming other broker logs. | | `hub.env-secrets` | `pkg/hub/handlers.go` (env/secret handlers) | Environment variable and secret management. Sensitive operations worth isolating. | | `broker.env-secrets` | `pkg/runtimebroker/handlers.go` (finalizeEnv, secret resolution) | Broker-side environment gathering and finalization — multi-step process with detailed debug logging. | -| `hub.templates` | `pkg/hub/template_handlers.go`, `template_bootstrap.go`, `harness_config_handlers.go` | Template CRUD, hydration, and hub-native grove bootstrap. | +| `hub.templates` | `pkg/hub/template_handlers.go`, `template_bootstrap.go`, `harness_config_handlers.go` | Template CRUD, hydration, and hub-managed grove bootstrap. | | `hub.workspace` | `pkg/hub/workspace_handlers.go`, `grove_workspace_handlers.go` | Git worktree sync operations. Failures here can be subtle and hard to trace. | ### Tier 3: Lower Priority diff --git a/.design/project-log/2026-05-09-final-hub-cleanup.md b/.design/project-log/2026-05-09-final-hub-cleanup.md index 708237831..b17c1293b 100644 --- a/.design/project-log/2026-05-09-final-hub-cleanup.md +++ b/.design/project-log/2026-05-09-final-hub-cleanup.md @@ -22,7 +22,7 @@ Renamed remaining internal Go functions in `pkg/hub/` from "Grove" to "Project" ### Test Fixes and Adjustments - **pkg/hub/response_types.go**: Fixed `ProjectWithCapabilities` to use anonymous embedding of `store.Project`. This restored the original JSON structure (fields at top level instead of under a `"project"` key), which was breaking several GET handlers and their tests. -- **pkg/hub/handlers_project_test.go**: Updated `TestCreateProject_HubNative_NoGitRemote` to expect the slug `hub-native-project` instead of `hub-native-grove`, reflecting the name change from "Hub Native Grove" to "Hub Native Project". +- **pkg/hub/handlers_project_test.go**: Updated `TestCreateProject_HubManaged_NoGitRemote` to expect the slug `hub-managed-project` instead of `hub-managed-grove`, reflecting the name change from "Hub Native Grove" to "Hub Native Project". - **pkg/hub/messagebroker_test.go**: - Updated assertions to expect 2 persisted messages in certain tests. This is due to `PublishUserMessage` performing local delivery (persistence) AND the `InProcessBroker` triggering the subscription handler (which also performs persistence). This behavior became reliable after fixing the project bootstrap logic. - Updated all test names and variable names to use "Project" instead of "Grove". diff --git a/.design/project-log/2026-05-31-hub-managed-naming.md b/.design/project-log/2026-05-31-hub-managed-naming.md new file mode 100644 index 000000000..1131e1d82 --- /dev/null +++ b/.design/project-log/2026-05-31-hub-managed-naming.md @@ -0,0 +1,28 @@ +# Hub-managed naming convergence + +**Date:** 2026-05-31 +**PR:** #110 +**Issue:** #96 + +## What + +Converged all "hub-native" / "Hub Workspace" naming to the canonical "hub-managed" across the entire codebase: Go identifiers, comments, TypeScript types, web UI labels, documentation, design docs, and changelogs. + +## Scope + +- 65 files changed across Go source, TypeScript, and markdown +- Renamed 5 Go functions/identifiers, 1 constant (with value change), updated ~100 comments +- Changed `ProjectType` wire value from `"hub-native"` to `"hub-managed"` +- Updated web UI label from "Hub Workspace" to "Hub-managed Workspace" + +## Key decisions + +- **Wire value changed**: The `projectType` API field changes from `"hub-native"` to `"hub-managed"`. This is safe because the value is computed (not stored in DB), and the project is in alpha with no backward compatibility requirement. +- **Test fixture names updated**: Test data using "Hub Native" in project names was updated to "Hub Managed" to ensure auto-derived slugs match expected values. +- **Generic "hub workspace" left alone**: References like `cmd/sync.go`'s "Pull hub workspace to local" refer to the generic concept (workspace on the hub), not the product term, and were left unchanged. + +## Process notes + +- Bulk `replace_all` edits worked well for identifier renames within files +- Careful to verify test fixture strings that were bulk-renamed didn't break slug derivation (caught one test failure: `TestCreateProject_HubManaged_NoGitRemote`) +- Pre-existing CI format issues in `extras/` files are unrelated to this change diff --git a/.design/project-log/content-aware-fallback.md b/.design/project-log/content-aware-fallback.md index cfe400123..d3e74670b 100644 --- a/.design/project-log/content-aware-fallback.md +++ b/.design/project-log/content-aware-fallback.md @@ -23,17 +23,17 @@ Replaced all `os.IsNotExist`-based fallback checks with a content-aware `hasWork - `handleStartAgent` workspace directory resolution (line ~601) - `deleteGrove` path resolution (line ~2283) -4. **`pkg/hub/handlers_grove_test.go`** — Added `TestHubNativeGrovePath_EmptyProjectsFallsBackToGroves` which creates a `projects/` dir with only `shared-dirs/` and `.scion/`, a `groves/` dir with real content, and verifies the fallback returns the groves path. +4. **`pkg/hub/handlers_grove_test.go`** — Added `TestHubManagedGrovePath_EmptyProjectsFallsBackToGroves` which creates a `projects/` dir with only `shared-dirs/` and `.scion/`, a `groves/` dir with real content, and verifies the fallback returns the groves path. ### Design Decisions - **Duplicated `hasWorkspaceContent` across packages** rather than extracting to a shared utility. The function is small (15 lines) and the two packages (`hub` and `runtimebroker`) have no shared internal utility package. Duplication is acceptable here. -- **Did NOT modify `server.go`** — the `findAgentInHubNativeGroves` and `discoverAuxiliaryRuntimes` functions already iterate both directories correctly. +- **Did NOT modify `server.go`** — the `findAgentInHubManagedGroves` and `discoverAuxiliaryRuntimes` functions already iterate both directories correctly. ## Verification - `go build ./...` passes - `go vet ./pkg/hub/ ./pkg/runtimebroker/` passes -- All existing `TestHubNative*` tests pass +- All existing `TestHubManaged*` tests pass - New empty-directory fallback test passes - Pre-existing `TestMessageBrokerProxy_*` failures are unrelated (reproduce on base branch) diff --git a/.design/project-log/path-traversal-fix.md b/.design/project-log/path-traversal-fix.md index f4ea1cbe0..61aa9bfe6 100644 --- a/.design/project-log/path-traversal-fix.md +++ b/.design/project-log/path-traversal-fix.md @@ -19,7 +19,7 @@ The `strings.HasPrefix` check now validates the resolved grove path against BOTH ## Test Updates Three tests were asserting the old `"groves"` default path. Since the fallback logic now returns `"projects"` when neither directory exists on disk (the common case in test environments), these were updated: -- `TestHubNativeGrovePath` in `pkg/hub/handlers_grove_test.go` +- `TestHubManagedGrovePath` in `pkg/hub/handlers_grove_test.go` - `TestCreateAgentGroveSlugResolvesGrovePath` in `pkg/runtimebroker/handlers_test.go` - `TestStartAgentGroveSlugResolvesGrovePath` in `pkg/runtimebroker/handlers_test.go` @@ -27,4 +27,4 @@ Three tests were asserting the old `"groves"` default path. Since the fallback l - `go build ./...` — passes - `go vet ./...` — passes -- All targeted tests pass: `TestDeleteGrove_PathTraversal_Blocked`, `TestCreateAgentGroveSlugResolvesGrovePath`, `TestStartAgentGroveSlugResolvesGrovePath`, `TestHubNativeGrovePath`, `TestFindAgentInHubNativeGroves` +- All targeted tests pass: `TestDeleteGrove_PathTraversal_Blocked`, `TestCreateAgentGroveSlugResolvesGrovePath`, `TestStartAgentGroveSlugResolvesGrovePath`, `TestHubManagedGrovePath`, `TestFindAgentInHubManagedGroves` diff --git a/.design/project-log/workspace-path-fallback.md b/.design/project-log/workspace-path-fallback.md index a48db90df..600681ff3 100644 --- a/.design/project-log/workspace-path-fallback.md +++ b/.design/project-log/workspace-path-fallback.md @@ -9,9 +9,9 @@ The grove-to-project rename (V50 SQL migration) renamed database tables and colu ## Solution -Applied a filesystem fallback pattern to all 6 code paths that resolve hub-native project/grove paths. The pattern is: try `projects/` first, if it doesn't exist check `groves/`, default to `projects/`. This matches the existing pattern already used in `ExternalProjectPath()` (`pkg/config/project_marker.go`). +Applied a filesystem fallback pattern to all 6 code paths that resolve hub-managed project/grove paths. The pattern is: try `projects/` first, if it doesn't exist check `groves/`, default to `projects/`. This matches the existing pattern already used in `ExternalProjectPath()` (`pkg/config/project_marker.go`). -For directory-scanning functions (`findAgentInHubNativeGroves`, `discoverAuxiliaryRuntimes`), both `projects/` and `groves/` directories are scanned and results merged. +For directory-scanning functions (`findAgentInHubManagedGroves`, `discoverAuxiliaryRuntimes`), both `projects/` and `groves/` directories are scanned and results merged. ## Files Changed @@ -22,7 +22,7 @@ For directory-scanning functions (`findAgentInHubNativeGroves`, `discoverAuxilia | `pkg/runtimebroker/handlers.go` | `createAgent()` line ~390 | Added fallback for `req.GrovePath` resolution | | `pkg/runtimebroker/handlers.go` | `createAgent()` line ~598 | Added fallback for `workspaceDir` resolution | | `pkg/runtimebroker/handlers.go` | `deleteGrove()` | Added fallback + updated path traversal check to use `filepath.Dir()` | -| `pkg/runtimebroker/handlers.go` | `findAgentInHubNativeGroves()` | Scans both `projects/` and `groves/` directories | +| `pkg/runtimebroker/handlers.go` | `findAgentInHubManagedGroves()` | Scans both `projects/` and `groves/` directories | | `pkg/runtimebroker/server.go` | `discoverAuxiliaryRuntimes()` | Scans both `projects/` and `groves/` directories | ## Notes diff --git a/.design/workspace-sync.md b/.design/workspace-sync.md index 6e532afa7..3ecd93f88 100644 --- a/.design/workspace-sync.md +++ b/.design/workspace-sync.md @@ -15,8 +15,8 @@ Consider these scenarios the current design doesn't address well: 1. **Multi-broker grove**: A linked grove exists on Broker A and Broker B. A developer makes changes on Broker A and wants them reflected on Broker B before dispatching agents there. 2. **Local-to-hub bootstrap**: A developer has a local project directory and wants to push the full grove workspace to the hub for remote agents — not tied to any specific agent. -3. **Hub-native grove download**: A hub-native grove's workspace was populated by agents. The developer wants to pull the current state to their local machine. -4. **Non-git groves**: Hub-native groves have no git remote. There's no git-based mechanism to propagate changes — file-level sync is the only option. +3. **Hub-managed grove download**: A hub-managed grove's workspace was populated by agents. The developer wants to pull the current state to their local machine. +4. **Non-git groves**: Hub-managed groves have no git remote. There's no git-based mechanism to propagate changes — file-level sync is the only option. ### 1.1 Key Insight @@ -211,7 +211,7 @@ Divergence is resolved naturally: ### 4.6 Hub-Hosted vs Broker-Hosted Workspaces -For **hub-native groves**, the workspace files live on the hub's filesystem. WebDAV serves them directly — zero relay needed. +For **hub-managed groves**, the workspace files live on the hub's filesystem. WebDAV serves them directly — zero relay needed. For **linked groves**, the workspace lives on a broker. The hub must relay WebDAV operations to the broker that owns the workspace. Two sub-approaches: @@ -322,7 +322,7 @@ The hub tracks sync state per grove: ```sql CREATE TABLE grove_sync_state ( grove_id TEXT NOT NULL, - broker_id TEXT, -- NULL for hub-native groves + broker_id TEXT, -- NULL for hub-managed groves last_sync_time TIMESTAMP, last_commit_sha TEXT, -- for git history sync file_count INTEGER, @@ -412,7 +412,7 @@ CREATE TABLE grove_sync_state ( - ✅ Implement WebDAV handler using `golang.org/x/net/webdav` - ✅ Mount at `/api/v1/groves/{id}/dav/` with grove-scoped authorization -- ✅ Serve hub-native grove workspaces directly from filesystem +- ✅ Serve hub-managed grove workspaces directly from filesystem - ✅ File exclusion filter (`.git/`, `.scion/`, `node_modules/`, `*.env`) - ✅ Add `grove_sync_state` table for tracking sync metadata - ✅ Add `GET /api/v1/groves/{id}/sync/status` endpoint @@ -446,7 +446,7 @@ CREATE TABLE grove_sync_state ( ## 10. References - [Hosted Workspace Sync Design](hosted/sync-design.md) — existing agent-scoped sync (GCS signed URLs) -- [Hub-Native Groves](hosted/hub-groves.md) — non-git grove workspaces +- [Hub-Managed Groves](hosted/hub-groves.md) — non-git grove workspaces - [Git-Anchored Groves](hosted/git-groves.md) — git-backed grove creation and cloning - [Multi-Hub Broker](hosted/multi-hub-broker.md) — broker identity and grove-to-hub mapping - [Git Workspace Hybrid](git-workspace-hybrid.md) — hybrid git/file workspace approaches diff --git a/.tasks/grove-rename-survey-v2.md b/.tasks/grove-rename-survey-v2.md index db1ebc78d..7f025e5c6 100644 --- a/.tasks/grove-rename-survey-v2.md +++ b/.tasks/grove-rename-survey-v2.md @@ -181,7 +181,7 @@ All `--grove` flags are properly deprecated with `MarkDeprecated` + `MarkHidden` | Path Pattern | Used For | Files | |---|---|---| | `grove-configs/` | Legacy project configs dir | `pkg/config/paths.go:32`, `pkg/config/project_marker.go:93`, `pkg/config/project_discovery.go:97,359`, `pkg/config/shared_dirs.go:47` | -| `.scion.groves//` | Hub-native project workspace | `pkg/runtimebroker/start_context.go:81,100,115`, `pkg/runtimebroker/handlers.go:603,892` | +| `.scion.groves//` | Hub-managed project workspace | `pkg/runtimebroker/start_context.go:81,100,115`, `pkg/runtimebroker/handlers.go:603,892` | | `grove-id` | Legacy project ID file | `pkg/config/project_marker.go:219-231`, `pkg/config/settings.go:552-557` | | `grove-workspace` | Shared workspace path segment | `pkg/storage/storage.go:255` | | `groves/` | Legacy projects directory | `pkg/config/paths.go:33`, `pkg/runtimebroker/server.go:870`, `pkg/runtimebroker/start_context.go:100` | diff --git a/agents.md b/agents.md index 667d4e4c3..fd3886603 100644 --- a/agents.md +++ b/agents.md @@ -87,7 +87,7 @@ All icons in the web frontend use the Shoelace `` component (Bootstrap These terms may be used in shorthand with prompts - **hub-broker, combo server** References running the server command with both the hub function and the broker function running in the same invocation. -- **hub-native, hub-project** A special variant of a project/project space, that is created on a hub server for use by agents dispatched from clients. These live in ~/.scion/projects/ on any broker that is a provider to the hub project. This is in contrast to the arbitrary local path on a broker for a linked project. +- **hub-managed, hub-project** A special variant of a project/project space, that is created on a hub server for use by agents dispatched from clients. These live in ~/.scion/projects/ on any broker that is a provider to the hub project. This is in contrast to the arbitrary local path on a broker for a linked project. - **agent-home** The directory that gets mounted as the home folder of the container user in the agent container - **linked-project** A project and project folder that pre-existed on a broker machine, and is linked as a hub resource project for visibility, metadata, and agent management across other brokers that may have such a linked project. May be based on name or git-URI diff --git a/changelog/2026-02-23-changelog.md b/changelog/2026-02-23-changelog.md index e56f20b63..4c6bd4f43 100644 --- a/changelog/2026-02-23-changelog.md +++ b/changelog/2026-02-23-changelog.md @@ -1,11 +1,11 @@ # Release Notes (Feb 23, 2026) -This period focused on major architectural expansions, introducing multi-hub connectivity for runtime brokers and "hub-native" groves that decouple workspace management from external Git repositories. +This period focused on major architectural expansions, introducing multi-hub connectivity for runtime brokers and "hub-managed" groves that decouple workspace management from external Git repositories. ## 🚀 Features * **Multi-Hub Broker Architecture:** Completed a major refactor of the Runtime Broker to support simultaneous connections to multiple Hubs. This includes a new multi-credential store, per-connection heartbeat management, and a "combo mode" that allows a broker to be co-located with one Hub while serving others remotely. -* **Hub-Native Groves:** Launched "Hub-Native" groves, enabling the creation of project workspaces directly through the Hub API and Web UI without an external Git repository. These groves are automatically initialized with a seeded `.scion` structure and managed locally by the Hub. -* **Streamlined Workspace Creation:** Introduced a new grove creation interface in the Web UI that supports both Git-based repositories and Hub-native workspaces, including direct Git URL support for quick onboarding. +* **Hub-Managed Groves:** Launched "Hub-Managed" groves, enabling the creation of project workspaces directly through the Hub API and Web UI without an external Git repository. These groves are automatically initialized with a seeded `.scion` structure and managed locally by the Hub. +* **Streamlined Workspace Creation:** Introduced a new grove creation interface in the Web UI that supports both Git-based repositories and Hub-managed workspaces, including direct Git URL support for quick onboarding. * **Improved Agent Configuration:** Enhanced the agent creation form with optimized dropdowns and more intuitive labeling, including renaming "Harness" to "Type" for better clarity. ## 🐛 Fixes diff --git a/changelog/2026-02-24-changelog.md b/changelog/2026-02-24-changelog.md index 84179d97b..6d2ead1b0 100644 --- a/changelog/2026-02-24-changelog.md +++ b/changelog/2026-02-24-changelog.md @@ -1,6 +1,6 @@ # Release Notes (Feb 24, 2026) -This release introduces a robust policy-based authorization system, a comprehensive agent notification framework, and significant enhancements to hub-native groves and schema validation. +This release introduces a robust policy-based authorization system, a comprehensive agent notification framework, and significant enhancements to hub-managed groves and schema validation. ## ⚠️ BREAKING CHANGES * **Policy-Based Authorization:** Strictly enforced authorization for agent operations. Agent creation now requires grove membership, while interaction (PTY, messaging) and deletion are restricted to the agent's owner (creator) or system administrators. @@ -9,7 +9,7 @@ This release introduces a robust policy-based authorization system, a comprehens * **Agent Notifications System:** Launched a multi-phase notification framework enabling real-time subscriptions to agent status events. This includes a new notification dispatcher, Hub API endpoints, and a `--notify` flag in the CLI for status tracking. * **Harness-Agnostic Templates:** Introduced support for role-based, harness-agnostic agent templates. New fields for `agent_instructions`, `system_prompt`, and `default_harness_config` allow templates to be defined by their role rather than specific LLM implementations. * **GKE Security Enhancements:** Added a dedicated `gke` runtime configuration option to enable GKE-specific features like Workload Identity, streamlining secure deployments on Google Kubernetes Engine. -* **Hub-Native Workspace Management:** Advanced hub-native grove capabilities (Phase 3) with new support for direct workspace file management via the Hub API, reducing reliance on external Git repositories. +* **Hub-Managed Workspace Management:** Advanced hub-managed grove capabilities (Phase 3) with new support for direct workspace file management via the Hub API, reducing reliance on external Git repositories. * **ADK Agent Integration:** Added a specialized example and Docker template for Agent Development Kit (ADK) agents, facilitating the development of custom autonomous agents within the Scion ecosystem. * **Infrastructure & Models:** Upgraded the default agent model to `gemini-3-flash-preview` and introduced Cloud Build configurations for automated image delivery. diff --git a/changelog/2026-02-25-changelog.md b/changelog/2026-02-25-changelog.md index 2a956f80e..b3ca6a112 100644 --- a/changelog/2026-02-25-changelog.md +++ b/changelog/2026-02-25-changelog.md @@ -11,6 +11,6 @@ This release focuses on hardening the agent provisioning pipeline, streamlining * **Agent Provisioning & Creation:** Resolved multiple issues in the Hub-dispatched agent creation flow, including a 403 authorization fix, rejection of duplicate agent names, and a critical fix for container image resolution. * **Instruction Injection Logic:** Improved the reliability of agent instructions by implementing auto-detection for `agents.md` and ensuring stale instruction files (e.g., lowercase `claude.md`) are removed during provisioning. * **Web UI & Auth Persistence:** Fixed a bug where the authenticated user wasn't correctly fetched on page load, ensuring the profile and sign-out options are always visible in the header. -* **Pathing & Scoping:** Corrected path resolution logic to prevent local-path groves from incorrectly using hub-native paths, and refined the `scion delete --stopped` command to strictly scope to the active grove. +* **Pathing & Scoping:** Corrected path resolution logic to prevent local-path groves from incorrectly using hub-managed paths, and refined the `scion delete --stopped` command to strictly scope to the active grove. * **Environment Gathering:** Fixed a regression in the `env-gather` finalize-env flow to ensure the template slug is correctly preserved throughout the entire provisioning pipeline. * **Configuration Schema:** Added `task_flag` support to the settings schema and Hub configuration, improving the tracking and validation of agent task states. diff --git a/changelog/2026-02-26-changelog.md b/changelog/2026-02-26-changelog.md index 89d9af56f..bba31c0f7 100644 --- a/changelog/2026-02-26-changelog.md +++ b/changelog/2026-02-26-changelog.md @@ -12,7 +12,7 @@ This release introduces a robust capability-based access control system, a dedic ## 🐛 Fixes * **Filesystem Session Store:** Replaced cookie-based session storage with a filesystem-backed store to resolve "400 Bad Request" errors caused by cookie size limits (4096 bytes) during large JWT/OAuth exchanges. -* **Hub-Native Grove Reliability:** Fixed critical 503 errors and path resolution issues during agent creation in hub-native groves by correctly propagating grove slugs to runtime brokers. +* **Hub-Managed Grove Reliability:** Fixed critical 503 errors and path resolution issues during agent creation in hub-managed groves by correctly propagating grove slugs to runtime brokers. * **Agent Deletion Cleanup:** Hardened the agent deletion flow to ensure that stopping and removing an agent in the Hub correctly dispatches cleanup commands to the associated runtime broker and removes local workspace files. * **Environment Validation:** Improved agent startup safety by treating missing required environment variables as fatal errors (422), preventing agents from starting in incomplete states. * **Terminal Responsiveness:** Resolved several layout bugs in the web terminal, ensuring it correctly resizes with the viewport and fits within the application shell. diff --git a/changelog/2026-02-27-changelog.md b/changelog/2026-02-27-changelog.md index 0f05db8ba..6f72d7a8c 100644 --- a/changelog/2026-02-27-changelog.md +++ b/changelog/2026-02-27-changelog.md @@ -1,6 +1,6 @@ # Release Notes (Feb 27, 2026) -This release focuses on refining the hub-native grove experience, enhancing the web terminal's usability, and introducing new workspace management capabilities via the Hub API. +This release focuses on refining the hub-managed grove experience, enhancing the web terminal's usability, and introducing new workspace management capabilities via the Hub API. ## 🚀 Features * **Workspace Management:** Added new Hub API endpoints for downloading individual workspace files and generating ZIP archives of entire groves, facilitating easier data export and backup. @@ -9,7 +9,7 @@ This release focuses on refining the hub-native grove experience, enhancing the * **Iconography Standardization:** Established a centralized icon reference system and updated the web interface to use consistent iconography for resources like groves, templates, and brokers. ## 🐛 Fixes -* **Hub-Native Path Resolution:** Resolved several critical issues where hub-native groves incorrectly inherited local filesystem paths from the Hub server. Broker-side initialization of `.scion` directories and explicit path mapping now ensure consistent workspace behavior across distributed brokers. +* **Hub-Managed Path Resolution:** Resolved several critical issues where hub-managed groves incorrectly inherited local filesystem paths from the Hub server. Broker-side initialization of `.scion` directories and explicit path mapping now ensure consistent workspace behavior across distributed brokers. * **Terminal & Clipboard UX:** Enabled native clipboard copy/paste support in the web terminal and relaxed availability checks to allow terminal access during agent startup and transition states. * **Real-time Data Integrity:** Fixed a bug in the frontend state manager where SSE delta updates could merge incorrectly; the manager is now reliably seeded with full REST data upon page load. * **Slug & Case Sensitivity:** Normalized agent slug lookups to lowercase and implemented stricter name validation to prevent routing collisions and inconsistent dispatcher behavior. diff --git a/changelog/2026-02-28-changelog.md b/changelog/2026-02-28-changelog.md index 329b6dc40..b4dc97389 100644 --- a/changelog/2026-02-28-changelog.md +++ b/changelog/2026-02-28-changelog.md @@ -12,7 +12,7 @@ This release marks a major milestone with the completion of the canonical agent * **Real-time Debug Observability:** Introduced a full-height debug panel in the Web UI, providing a real-time stream of SSE events and internal state transitions for advanced troubleshooting and observability. * **Enhanced Web UI Feedback:** Added emoji-based status badges to agent cards and list views, providing more intuitive visual indicators of agent health and activity. * **Broker Authorization & Identity:** Strengthened security by enforcing dispatch authorization checks and resolving creator identities for all registered runtime brokers. -* **Automated Grove Cleanup:** Hardened the hub-native grove lifecycle by implementing cascaded directory cleanup on remote brokers whenever a grove is deleted via the Hub. +* **Automated Grove Cleanup:** Hardened the hub-managed grove lifecycle by implementing cascaded directory cleanup on remote brokers whenever a grove is deleted via the Hub. * **CLI Enhancements:** Added a new `-n/--num-lines` flag to the `scion look` command, enabling tailored views of agent terminal output. ## 🐛 Fixes diff --git a/changelog/2026-03-11-changelog.md b/changelog/2026-03-11-changelog.md index 18f015e67..777bad65c 100644 --- a/changelog/2026-03-11-changelog.md +++ b/changelog/2026-03-11-changelog.md @@ -15,7 +15,7 @@ This release focuses on improving agent lifecycle flexibility and enhancing the * **Authentication & Secret Injection:** Corrected a bug where environment-type secrets were not properly injected into the execution environment during authentication resolution. * **Grove & Workspace Management:** * **Multi-Hub Compatibility:** Fixed a regression where git-based groves were incorrectly rejected in multi-hub environments. - * **Cleanup & Resolution:** Improved hub-native grove path resolution during agent deletion and enhanced detection of orphaned grove configurations. + * **Cleanup & Resolution:** Improved hub-managed grove path resolution during agent deletion and enhanced detection of orphaned grove configurations. * **Configuration & Compatibility:** * **Legacy Key Support:** Updated `config get` to support legacy v1 settings keys like `image_registry`. * **Fallback Logic:** Improved `env-gather` and harness configuration to correctly fall back to global settings when local context is missing. diff --git a/changelog/2026-03-14-changelog.md b/changelog/2026-03-14-changelog.md index 6d383c553..1f2b20414 100644 --- a/changelog/2026-03-14-changelog.md +++ b/changelog/2026-03-14-changelog.md @@ -3,7 +3,7 @@ This release introduces the foundational infrastructure for the Scion plugin system, adds comprehensive support for syncing grove-level templates, and unifies all Grove IDs to a standard UUID format. ## ⚠️ BREAKING CHANGES -* **Grove ID Format Unification:** All Grove IDs have been standardized to a unified UUID format. Git-backed groves now use a deterministic UUID v5 (based on the namespace and normalized URL) instead of a 16-character hex hash, while non-git and hub-native groves continue using UUID v4. Existing git-backed groves may need to be re-linked, and any integrations relying on the old hex format must be updated (commit e896693). +* **Grove ID Format Unification:** All Grove IDs have been standardized to a unified UUID format. Git-backed groves now use a deterministic UUID v5 (based on the namespace and normalized URL) instead of a 16-character hex hash, while non-git and hub-managed groves continue using UUID v4. Existing git-backed groves may need to be re-linked, and any integrations relying on the old hex format must be updated (commit e896693). ## 🚀 Features * **Plugin System Infrastructure:** Introduced the core architecture for a new Scion plugin system using `hashicorp/go-plugin`, complete with reference implementations for message broker and agent harness plugins (consolidated from commits 6c543d0, b1a5ae1, 22991ec). diff --git a/changelog/2026-03-17-changelog.md b/changelog/2026-03-17-changelog.md index fd9503a0b..d406842a8 100644 --- a/changelog/2026-03-17-changelog.md +++ b/changelog/2026-03-17-changelog.md @@ -10,7 +10,7 @@ This release introduces a major new GCP Identity implementation allowing agents ## 🐛 Fixes * **Grove & Membership Synchronization:** Resolved multiple issues with grove linking and membership backfills, including fixing unique constraints on grove IDs, ensuring proper legacy owner role assignments, and correctly including auto-provide brokers (consolidated from commits 4af2662, 307fb85, cb22a18, 79cc591, 1f6f16f, e14ec95). -* **Storage & ID Consistency:** Fixed global grove ID bleed-through issues and unified agent split storage paths under `.scion/` for deterministic behavior across hub-native and external groves. Ensured cascading cleanups of templates and configs when a grove is deleted (consolidated from commits fea4588, 6bb2348, a97ebd7, 023a089, 6eaf8dc, 221c736, 75bfcc0, c9d8ddf). +* **Storage & ID Consistency:** Fixed global grove ID bleed-through issues and unified agent split storage paths under `.scion/` for deterministic behavior across hub-managed and external groves. Ensured cascading cleanups of templates and configs when a grove is deleted (consolidated from commits fea4588, 6bb2348, a97ebd7, 023a089, 6eaf8dc, 221c736, 75bfcc0, c9d8ddf). * **GCP Validation & Logging:** Improved debug logging for 4xx errors and enhanced GCP Service Account validation messages, including returning capabilities in the list API response (consolidated from commits e060664, d65dc09). * **Container Lifecycle Management:** Ensured agent containers are gracefully stopped before removal to prevent shared-directory mount errors (commit 8a0fabc). * **Template Synchronization:** Fixed an issue where template synchronization was blocked by setting a default image for the generic harness config (commit 816c960). diff --git a/cmd/server_dispatcher.go b/cmd/server_dispatcher.go index 930354f1d..bd71885f9 100644 --- a/cmd/server_dispatcher.go +++ b/cmd/server_dispatcher.go @@ -226,7 +226,7 @@ func (d *agentDispatcherAdapter) DispatchAgentDelete(ctx context.Context, hubAge } } - // For hub-native groves the provider LocalPath is typically empty. + // For hub-managed projects the provider LocalPath is typically empty. // Resolve from the grove slug so file cleanup can find the agent // directory at ~/.scion/groves//.scion/agents/. if projectPath == "" && hubAgent.ProjectID != "" && deleteFiles { diff --git a/docs-site/src/content/docs/advanced-local/workspace.md b/docs-site/src/content/docs/advanced-local/workspace.md index 18765c74f..93fddfb49 100644 --- a/docs-site/src/content/docs/advanced-local/workspace.md +++ b/docs-site/src/content/docs/advanced-local/workspace.md @@ -83,8 +83,8 @@ In non-git projects (where no `.git` directory is found): When a Scion Hub is enabled, workspace strategy changes depending on the project type. The Hub supports three types of remote workspaces: -### Hub-Native Projects (no git repository) -Hub-Native projects allow you to create project workspaces directly through the Hub API and Web Dashboard **without an external Git repository**. +### Hub-Managed Projects (no git repository) +Hub-Managed projects allow you to create project workspaces directly through the Hub API and Web Dashboard **without an external Git repository**. - The Hub automatically initializes a seeded `.scion` structure. - Workspace files are managed locally by the Hub and its distributed runtime brokers. - You can directly download individual workspace files or generate ZIP archives of entire projects using the Hub API or Web Dashboard, making it easy to export your data. @@ -106,7 +106,7 @@ scion hub project create https://github.com/org/repo.git #### Project ID Format -Git-backed projects use **deterministic UUID v5** identifiers derived from the namespace and the normalized git URL. This ensures the same repository always produces the same project ID regardless of the access protocol (e.g., `https://` vs `git@`). Hub-native projects use random UUID v4 identifiers. +Git-backed projects use **deterministic UUID v5** identifiers derived from the namespace and the normalized git URL. This ensures the same repository always produces the same project ID regardless of the access protocol (e.g., `https://` vs `git@`). Hub-managed projects use random UUID v4 identifiers. #### Agent Branch Strategy diff --git a/docs-site/src/content/docs/concepts.md b/docs-site/src/content/docs/concepts.md index d96794c85..ddbd35e06 100644 --- a/docs-site/src/content/docs/concepts.md +++ b/docs-site/src/content/docs/concepts.md @@ -12,7 +12,7 @@ An **Agent** is an isolated process running an LLM + Harness loop (aka Agent) ag ### Project A **Project** (or **Group**) is a project workspace where agents live. It corresponds to a `.scion` directory on the filesystem. It can exist at the project level (generally located at the root of a git repository), or globally in the users home folder. -Every project has a unique **Project ID**. Git-backed projects use deterministic **UUID v5** identifiers (derived from the namespace and normalized git URL), ensuring the same repository always maps to the same ID regardless of protocol. Hub-native projects use random **UUID v4** identifiers. +Every project has a unique **Project ID**. Git-backed projects use deterministic **UUID v5** identifiers (derived from the namespace and normalized git URL), ensuring the same repository always maps to the same ID regardless of protocol. Hub-managed projects use random **UUID v4** identifiers. ### Hub The **Hub** is the central control plane of a hosted Scion architecture. It acts as the "brain" of the system, coordinating state across multiple users, projects, and runtime brokers. diff --git a/docs-site/src/content/docs/glossary.md b/docs-site/src/content/docs/glossary.md index 1b3484f27..69549c186 100644 --- a/docs-site/src/content/docs/glossary.md +++ b/docs-site/src/content/docs/glossary.md @@ -33,7 +33,7 @@ A helper utility bundled with Scion that is injected into agent containers to pr A versioned blueprint for an agent, defining its base image, system prompt, tools, and initial state. ### Project ID -A unique identifier for a project. Git-backed projects use deterministic **UUID v5** identifiers derived from the normalized git URL. Hub-native projects use random **UUID v4** identifiers. +A unique identifier for a project. Git-backed projects use deterministic **UUID v5** identifiers derived from the normalized git URL. Hub-managed projects use random **UUID v4** identifiers. ### Plugin An extension module built on `hashicorp/go-plugin` that provides additional capabilities (e.g., message broker or agent harness implementations) without modifying the Scion core. diff --git a/docs-site/src/content/docs/hub-admin/hub-server.md b/docs-site/src/content/docs/hub-admin/hub-server.md index 733285a71..3ab90f5b8 100644 --- a/docs-site/src/content/docs/hub-admin/hub-server.md +++ b/docs-site/src/content/docs/hub-admin/hub-server.md @@ -120,7 +120,7 @@ The Hub provides a comprehensive UI for configuring project-level settings, ensu The Project Settings UI is organized into three primary tabs: -- **General**: Configure the project's display name, description, and template sync settings. For git-backed projects, you can specify default branches. For hub-native projects, you can configure external git repositories to load templates from. +- **General**: Configure the project's display name, description, and template sync settings. For git-backed projects, you can specify default branches. For hub-managed projects, you can configure external git repositories to load templates from. - **Limits**: Define constraints on agent execution to prevent resource exhaustion. - **Hub-level Defaults**: Administrators can configure global default limits that apply to all projects. - **Project-level Limits**: Overrides can be set per-project. @@ -131,7 +131,7 @@ The Project Settings UI is organized into three primary tabs: ### Template Synchronization -Projects support loading templates from external Git repositories, which is especially useful for non-Git-backed (hub-native) projects. The UI accepts bare host/org/repo URLs (e.g., `github.com/org/repo`) and automatically normalizes them, appending `/.scion/templates/` unless a deeper path is specified. This synchronization can be manually triggered via the UI to immediately pull the latest templates. +Projects support loading templates from external Git repositories, which is especially useful for non-Git-backed (hub-managed) projects. The UI accepts bare host/org/repo URLs (e.g., `github.com/org/repo`) and automatically normalizes them, appending `/.scion/templates/` unless a deeper path is specified. This synchronization can be manually triggered via the UI to immediately pull the latest templates. ## Server Maintenance & Updates diff --git a/docs-site/src/content/docs/hub-user/dashboard.md b/docs-site/src/content/docs/hub-user/dashboard.md index 0dc071789..689d035fd 100644 --- a/docs-site/src/content/docs/hub-user/dashboard.md +++ b/docs-site/src/content/docs/hub-user/dashboard.md @@ -20,7 +20,7 @@ The dashboard features an integrated notification framework with real-time SSE d ### Projects View and manage your registered projects. -- **Create/Register Project**: Create a Hub-Native workspace directly on the Hub, or connect a new remote Git repository. Includes a confirmation dialog when creating a project for an existing git repository. +- **Create/Register Project**: Create a Hub-Managed workspace directly on the Hub, or connect a new remote Git repository. Includes a confirmation dialog when creating a project for an existing git repository. - **Project Settings**: Centralized configuration interface for managing project-scoped environment variables and secrets, including "Injection Mode" controls (Always vs. As-Needed). The settings page features a streamlined flow with a "Done" button and hides unnecessary registration options for git-backed projects. - **Workspace & File Management**: Access the comprehensive **inline file editor** to view and modify files directly in the browser, featuring integrated Markdown preview capabilities. The file browser supports **fuzzy and regex-based filtering** for fast navigation. You can also download individual workspace files or generate ZIP archives of entire projects directly from the UI. - **Template Management**: Direct server-side importing of templates with immediate UI feedback. Includes full template file browsing, editing, and upload capabilities directly within the dashboard. diff --git a/docs-site/src/content/docs/hub-user/git-projects.md b/docs-site/src/content/docs/hub-user/git-projects.md index ffbcaa4ea..bd551100a 100644 --- a/docs-site/src/content/docs/hub-user/git-projects.md +++ b/docs-site/src/content/docs/hub-user/git-projects.md @@ -87,7 +87,7 @@ Project created: ``` :::note[Project ID Format] -Git-backed projects use **deterministic UUID v5** identifiers, derived from the namespace and normalized git URL. This ensures the same repository always produces the same project ID regardless of protocol (`https://` vs `git@`). Hub-native projects (without a git repository) use random UUID v4 identifiers. +Git-backed projects use **deterministic UUID v5** identifiers, derived from the namespace and normalized git URL. This ensures the same repository always produces the same project ID regardless of protocol (`https://` vs `git@`). Hub-managed projects (without a git repository) use random UUID v4 identifiers. ::: ### Optional Flags diff --git a/docs-site/src/content/docs/hub-user/hosted-user.md b/docs-site/src/content/docs/hub-user/hosted-user.md index cb6c5b88f..8f2017ea8 100644 --- a/docs-site/src/content/docs/hub-user/hosted-user.md +++ b/docs-site/src/content/docs/hub-user/hosted-user.md @@ -102,16 +102,16 @@ These can also be managed via the web UI at either the user scope (under the pro See the [Secret & Environment Management guide](/scion/hub-user/secrets/) for details on scoping and projection modes. -## Remote & Hub-Native Projects +## Remote & Hub-Managed Projects Instead of linking a local directory, you can create projects directly on the Hub. This decouples agent execution from your local machine, allowing for remote-only development. -### Hub-Native Projects -Hub-Native projects allow you to create project workspaces without any external Git repository. The Hub manages the workspace files directly, and you can download or ZIP the workspace via the Web Dashboard. +### Hub-Managed Projects +Hub-Managed projects allow you to create project workspaces without any external Git repository. The Hub manages the workspace files directly, and you can download or ZIP the workspace via the Web Dashboard. ```bash -# Target a Hub-Native project remotely by its slug: -scion start my-agent --project my-hub-native-slug "do some work" +# Target a Hub-Managed project remotely by its slug: +scion start my-agent --project my-hub-managed-slug "do some work" ``` ### Git Projects diff --git a/docs-site/src/content/docs/release-notes.md b/docs-site/src/content/docs/release-notes.md index a1d77ad59..e4ec7dd07 100644 --- a/docs-site/src/content/docs/release-notes.md +++ b/docs-site/src/content/docs/release-notes.md @@ -14,7 +14,7 @@ This release introduces a major new GCP Identity implementation allowing agents ### 🐛 Fixes * **Grove & Membership Synchronization:** Resolved multiple issues with grove linking and membership backfills, including fixing unique constraints on grove IDs, ensuring proper legacy owner role assignments, and correctly including auto-provide brokers (consolidated from commits 4af2662, 307fb85, cb22a18, 79cc591, 1f6f16f, e14ec95). -* **Storage & ID Consistency:** Fixed global grove ID bleed-through issues and unified agent split storage paths under `.scion/` for deterministic behavior across hub-native and external groves. Ensured cascading cleanups of templates and configs when a grove is deleted (consolidated from commits fea4588, 6bb2348, a97ebd7, 023a089, 6eaf8dc, 221c736, 75bfcc0, c9d8ddf). +* **Storage & ID Consistency:** Fixed global grove ID bleed-through issues and unified agent split storage paths under `.scion/` for deterministic behavior across hub-managed and external groves. Ensured cascading cleanups of templates and configs when a grove is deleted (consolidated from commits fea4588, 6bb2348, a97ebd7, 023a089, 6eaf8dc, 221c736, 75bfcc0, c9d8ddf). * **GCP Validation & Logging:** Improved debug logging for 4xx errors and enhanced GCP Service Account validation messages, including returning capabilities in the list API response (consolidated from commits e060664, d65dc09). * **Container Lifecycle Management:** Ensured agent containers are gracefully stopped before removal to prevent shared-directory mount errors (commit 8a0fabc). * **Template Synchronization:** Fixed an issue where template synchronization was blocked by setting a default image for the generic harness config (commit 816c960). @@ -60,7 +60,7 @@ This release significantly hardens the agent workspace provisioning process for This release introduces the foundational infrastructure for the Scion plugin system, adds comprehensive support for syncing grove-level templates, and unifies all Grove IDs to a standard UUID format. :::danger[BREAKING CHANGES] -* **Grove ID Format Unification:** All Grove IDs have been standardized to a unified UUID format. Git-backed groves now use a deterministic UUID v5 (based on the namespace and normalized URL) instead of a 16-character hex hash, while non-git and hub-native groves continue using UUID v4. Existing git-backed groves may need to be re-linked, and any integrations relying on the old hex format must be updated (commit e896693). +* **Grove ID Format Unification:** All Grove IDs have been standardized to a unified UUID format. Git-backed groves now use a deterministic UUID v5 (based on the namespace and normalized URL) instead of a 16-character hex hash, while non-git and hub-managed groves continue using UUID v4. Existing git-backed groves may need to be re-linked, and any integrations relying on the old hex format must be updated (commit e896693). ::: @@ -138,7 +138,7 @@ This release focuses on improving agent lifecycle flexibility and enhancing the * **Authentication & Secret Injection:** Corrected a bug where environment-type secrets were not properly injected into the execution environment during authentication resolution. * **Grove & Workspace Management:** * **Multi-Hub Compatibility:** Fixed a regression where git-based groves were incorrectly rejected in multi-hub environments. - * **Cleanup & Resolution:** Improved hub-native grove path resolution during agent deletion and enhanced detection of orphaned grove configurations. + * **Cleanup & Resolution:** Improved hub-managed grove path resolution during agent deletion and enhanced detection of orphaned grove configurations. * **Configuration & Compatibility:** * **Legacy Key Support:** Updated `config get` to support legacy v1 settings keys like `image_registry`. * **Fallback Logic:** Improved `env-gather` and harness configuration to correctly fall back to global settings when local context is missing. @@ -371,7 +371,7 @@ This release marks a major milestone with the completion of the canonical agent * **Real-time Debug Observability:** Introduced a full-height debug panel in the Web UI, providing a real-time stream of SSE events and internal state transitions for advanced troubleshooting and observability. * **Enhanced Web UI Feedback:** Added emoji-based status badges to agent cards and list views, providing more intuitive visual indicators of agent health and activity. * **Broker Authorization & Identity:** Strengthened security by enforcing dispatch authorization checks and resolving creator identities for all registered runtime brokers. -* **Automated Grove Cleanup:** Hardened the hub-native grove lifecycle by implementing cascaded directory cleanup on remote brokers whenever a grove is deleted via the Hub. +* **Automated Grove Cleanup:** Hardened the hub-managed grove lifecycle by implementing cascaded directory cleanup on remote brokers whenever a grove is deleted via the Hub. * **CLI Enhancements:** Added a new `-n/--num-lines` flag to the `scion look` command, enabling tailored views of agent terminal output. ### 🐛 Fixes @@ -381,7 +381,7 @@ This release marks a major milestone with the completion of the canonical agent ## Feb 27, 2026 -This release focuses on refining the hub-native grove experience, enhancing the web terminal's usability, and introducing new workspace management capabilities via the Hub API. +This release focuses on refining the hub-managed grove experience, enhancing the web terminal's usability, and introducing new workspace management capabilities via the Hub API. ### 🚀 Features * **Workspace Management:** Added new Hub API endpoints for downloading individual workspace files and generating ZIP archives of entire groves, facilitating easier data export and backup. @@ -390,7 +390,7 @@ This release focuses on refining the hub-native grove experience, enhancing the * **Iconography Standardization:** Established a centralized icon reference system and updated the web interface to use consistent iconography for resources like groves, templates, and brokers. ### 🐛 Fixes -* **Hub-Native Path Resolution:** Resolved several critical issues where hub-native groves incorrectly inherited local filesystem paths from the Hub server. Broker-side initialization of `.scion` directories and explicit path mapping now ensure consistent workspace behavior across distributed brokers. +* **Hub-Managed Path Resolution:** Resolved several critical issues where hub-managed groves incorrectly inherited local filesystem paths from the Hub server. Broker-side initialization of `.scion` directories and explicit path mapping now ensure consistent workspace behavior across distributed brokers. * **Terminal & Clipboard UX:** Enabled native clipboard copy/paste support in the web terminal and relaxed availability checks to allow terminal access during agent startup and transition states. * **Real-time Data Integrity:** Fixed a bug in the frontend state manager where SSE delta updates could merge incorrectly; the manager is now reliably seeded with full REST data upon page load. * **Slug & Case Sensitivity:** Normalized agent slug lookups to lowercase and implemented stricter name validation to prevent routing collisions and inconsistent dispatcher behavior. @@ -411,7 +411,7 @@ This release introduces a robust capability-based access control system, a dedic ### 🐛 Fixes * **Filesystem Session Store:** Replaced cookie-based session storage with a filesystem-backed store to resolve "400 Bad Request" errors caused by cookie size limits (4096 bytes) during large JWT/OAuth exchanges. -* **Hub-Native Grove Reliability:** Fixed critical 503 errors and path resolution issues during agent creation in hub-native groves by correctly propagating grove slugs to runtime brokers. +* **Hub-Managed Grove Reliability:** Fixed critical 503 errors and path resolution issues during agent creation in hub-managed groves by correctly propagating grove slugs to runtime brokers. * **Agent Deletion Cleanup:** Hardened the agent deletion flow to ensure that stopping and removing an agent in the Hub correctly dispatches cleanup commands to the associated runtime broker and removes local workspace files. * **Environment Validation:** Improved agent startup safety by treating missing required environment variables as fatal errors (422), preventing agents from starting in incomplete states. * **Terminal Responsiveness:** Resolved several layout bugs in the web terminal, ensuring it correctly resizes with the viewport and fits within the application shell. @@ -430,13 +430,13 @@ This release focuses on hardening the agent provisioning pipeline, streamlining * **Agent Provisioning & Creation:** Resolved multiple issues in the Hub-dispatched agent creation flow, including a 403 authorization fix, rejection of duplicate agent names, and a critical fix for container image resolution. * **Instruction Injection Logic:** Improved the reliability of agent instructions by implementing auto-detection for `agents.md` and ensuring stale instruction files (e.g., lowercase `claude.md`) are removed during provisioning. * **Web UI & Auth Persistence:** Fixed a bug where the authenticated user wasn't correctly fetched on page load, ensuring the profile and sign-out options are always visible in the header. -* **Pathing & Scoping:** Corrected path resolution logic to prevent local-path groves from incorrectly using hub-native paths, and refined the `scion delete --stopped` command to strictly scope to the active grove. +* **Pathing & Scoping:** Corrected path resolution logic to prevent local-path groves from incorrectly using hub-managed paths, and refined the `scion delete --stopped` command to strictly scope to the active grove. * **Environment Gathering:** Fixed a regression in the `env-gather` finalize-env flow to ensure the template slug is correctly preserved throughout the entire provisioning pipeline. * **Configuration Schema:** Added `task_flag` support to the settings schema and Hub configuration, improving the tracking and validation of agent task states. ## Feb 24, 2026 -This release introduces a robust policy-based authorization system, a comprehensive agent notification framework, and significant enhancements to hub-native groves and schema validation. +This release introduces a robust policy-based authorization system, a comprehensive agent notification framework, and significant enhancements to hub-managed groves and schema validation. :::danger[BREAKING CHANGES] * **Policy-Based Authorization:** Strictly enforced authorization for agent operations. Agent creation now requires grove membership, while interaction (PTY, messaging) and deletion are restricted to the agent's owner (creator) or system administrators. @@ -447,7 +447,7 @@ This release introduces a robust policy-based authorization system, a comprehens * **Agent Notifications System:** Launched a multi-phase notification framework enabling real-time subscriptions to agent status events. This includes a new notification dispatcher, Hub API endpoints, and a `--notify` flag in the CLI for status tracking. * **Harness-Agnostic Templates:** Introduced support for role-based, harness-agnostic agent templates. New fields for `agent_instructions`, `system_prompt`, and `default_harness_config` allow templates to be defined by their role rather than specific LLM implementations. * **GKE Security Enhancements:** Added a dedicated `gke` runtime configuration option to enable GKE-specific features like Workload Identity, streamlining secure deployments on Google Kubernetes Engine. -* **Hub-Native Workspace Management:** Advanced hub-native grove capabilities (Phase 3) with new support for direct workspace file management via the Hub API, reducing reliance on external Git repositories. +* **Hub-Managed Workspace Management:** Advanced hub-managed grove capabilities (Phase 3) with new support for direct workspace file management via the Hub API, reducing reliance on external Git repositories. * **ADK Agent Integration:** Added a specialized example and Docker template for Agent Development Kit (ADK) agents, facilitating the development of custom autonomous agents within the Scion ecosystem. * **Infrastructure & Models:** Upgraded the default agent model to `gemini-3-flash-preview` and introduced Cloud Build configurations for automated image delivery. @@ -459,12 +459,12 @@ This release introduces a robust policy-based authorization system, a comprehens ## Feb 23, 2026 -This period focused on major architectural expansions, introducing multi-hub connectivity for runtime brokers and "hub-native" groves that decouple workspace management from external Git repositories. +This period focused on major architectural expansions, introducing multi-hub connectivity for runtime brokers and "hub-managed" groves that decouple workspace management from external Git repositories. ### 🚀 Features * **Multi-Hub Broker Architecture:** Completed a major refactor of the Runtime Broker to support simultaneous connections to multiple Hubs. This includes a new multi-credential store, per-connection heartbeat management, and a "combo mode" that allows a broker to be co-located with one Hub while serving others remotely. -* **Hub-Native Groves:** Launched "Hub-Native" groves, enabling the creation of project workspaces directly through the Hub API and Web UI without an external Git repository. These groves are automatically initialized with a seeded `.scion` structure and managed locally by the Hub. -* **Streamlined Workspace Creation:** Introduced a new grove creation interface in the Web UI that supports both Git-based repositories and Hub-native workspaces, including direct Git URL support for quick onboarding. +* **Hub-Managed Groves:** Launched "Hub-Managed" groves, enabling the creation of project workspaces directly through the Hub API and Web UI without an external Git repository. These groves are automatically initialized with a seeded `.scion` structure and managed locally by the Hub. +* **Streamlined Workspace Creation:** Introduced a new grove creation interface in the Web UI that supports both Git-based repositories and Hub-managed workspaces, including direct Git URL support for quick onboarding. * **Improved Agent Configuration:** Enhanced the agent creation form with optimized dropdowns and more intuitive labeling, including renaming "Harness" to "Type" for better clarity. ### 🐛 Fixes diff --git a/pkg/config/project_marker.go b/pkg/config/project_marker.go index a8bab9751..73aaa7dab 100644 --- a/pkg/config/project_marker.go +++ b/pkg/config/project_marker.go @@ -193,7 +193,7 @@ func IsHubContext() bool { // WriteWorkspaceMarker writes a minimal .scion marker file into a workspace // directory so that in-container CLI can discover the project context. // This is called during agent provisioning for git projects (where the worktree -// doesn't contain .scion because it's gitignored) and for hub-native projects. +// doesn't contain .scion because it's gitignored) and for hub-managed projects. func WriteWorkspaceMarker(workspacePath string, projectID, projectName, projectSlug string) error { if projectID == "" || projectSlug == "" { return fmt.Errorf("project-id and project-slug are required for workspace marker") diff --git a/pkg/hub/brokerclient.go b/pkg/hub/brokerclient.go index e433841ba..b3dffcf54 100644 --- a/pkg/hub/brokerclient.go +++ b/pkg/hub/brokerclient.go @@ -88,7 +88,7 @@ func (c *AuthenticatedBrokerClient) ExecAgent(ctx context.Context, brokerID, bro return c.transport.ExecAgent(ctx, brokerID, brokerEndpoint, agentID, projectID, command, timeout) } -// CleanupProject asks a broker to remove its local hub-native project directory with HMAC authentication. +// CleanupProject asks a broker to remove its local hub-managed project directory with HMAC authentication. func (c *AuthenticatedBrokerClient) CleanupProject(ctx context.Context, brokerID, brokerEndpoint, projectSlug string) error { return c.transport.CleanupProject(ctx, brokerID, brokerEndpoint, projectSlug) } diff --git a/pkg/hub/embedded_broker_test.go b/pkg/hub/embedded_broker_test.go index 4a5b4beab..576f225e5 100644 --- a/pkg/hub/embedded_broker_test.go +++ b/pkg/hub/embedded_broker_test.go @@ -61,7 +61,7 @@ func TestCreateAgent_SkipsGCSSyncForEmbeddedBroker(t *testing.T) { srv, s := testServer(t) ctx := context.Background() - // Create a project (hub-native: no git remote) + // Create a project (hub-managed: no git remote) project := &store.Project{ ID: "project-embedded-test", Name: "embedded-test", @@ -103,7 +103,7 @@ func TestCreateAgent_SkipsGCSSyncForEmbeddedBroker(t *testing.T) { // Mark the broker as the embedded broker srv.SetEmbeddedBrokerID(brokerID) - // Create agent request for the hub-native project + // Create agent request for the hub-managed project reqBody := CreateAgentRequest{ Name: "test-agent", ProjectID: project.ID, diff --git a/pkg/hub/handlers.go b/pkg/hub/handlers.go index a2410dc65..a54845f69 100644 --- a/pkg/hub/handlers.go +++ b/pkg/hub/handlers.go @@ -778,7 +778,7 @@ func (s *Server) createAgentInProject( } } - // Hub-native/shared-workspace project remote broker support: if the project has + // Hub-managed/shared-workspace project remote broker support: if the project has // a managed workspace and the workspace path is set, upload it to GCS so // a remote broker can download it. if (project.GitRemote == "" || project.IsSharedWorkspace()) && agent.AppliedConfig != nil && agent.AppliedConfig.Workspace != "" { @@ -795,7 +795,7 @@ func (s *Server) createAgentInProject( if stor != nil { storagePath := storage.ProjectWorkspaceStoragePath(project.ID) if err := gcp.SyncToGCS(ctx, agent.AppliedConfig.Workspace, stor.Bucket(), storagePath+"/files"); err != nil { - s.agentLifecycleLog.Warn("Failed to upload hub-native project workspace to GCS", + s.agentLifecycleLog.Warn("Failed to upload hub-managed project workspace to GCS", "agent_id", agent.ID, "project_id", project.ID, "error", err) } else { @@ -2866,7 +2866,7 @@ func (s *Server) handleAgentLifecycle(w http.ResponseWriter, r *http.Request, id case api.AgentActionStop: newPhase = string(state.PhaseStopped) if dispatcher != nil && agent.RuntimeBrokerID != "" { - // Before stopping, sync workspace back for hub-native projects on remote brokers. + // Before stopping, sync workspace back for hub-managed projects on remote brokers. // This is best-effort: failures are logged but don't block the stop. s.syncWorkspaceOnStop(ctx, agent) dispatchErr = dispatcher.DispatchAgentStop(ctx, agent) @@ -3479,7 +3479,7 @@ func (s *Server) createProject(w http.ResponseWriter, r *http.Request) { } } - // Initialize filesystem workspace for hub-native projects and shared-workspace git projects. + // Initialize filesystem workspace for hub-managed projects and shared-workspace git projects. if project.IsSharedWorkspace() { // Shared-workspace git project: clone the repository into the workspace. // Clone failure is a creation failure — clean up the project record. @@ -3516,8 +3516,8 @@ func (s *Server) createProject(w http.ResponseWriter, r *http.Request) { return } } else if project.GitRemote == "" { - // Hub-native project (no git remote): create workspace directory. - if err := s.initHubNativeProject(project); err != nil { + // Hub-managed project (no git remote): create workspace directory. + if err := s.initHubManagedProject(project); err != nil { slog.Warn("failed to initialize project workspace", "project_id", project.ID, "slug", project.Slug, "error", err) } @@ -3733,8 +3733,8 @@ func (s *Server) createProjectMembersGroupAndPolicy(ctx context.Context, project } } -// hubNativeProjectPath returns the filesystem path for a hub-native project workspace. -func hubNativeProjectPath(slug string) (string, error) { +// hubManagedProjectPath returns the filesystem path for a hub-managed project workspace. +func hubManagedProjectPath(slug string) (string, error) { globalDir, err := config.GetGlobalDir() if err != nil { return "", fmt.Errorf("failed to get global dir: %w", err) @@ -3769,12 +3769,12 @@ func hasWorkspaceContent(dir string) bool { return false } -// initHubNativeProject initializes the filesystem workspace for a hub-native project. +// initHubManagedProject initializes the filesystem workspace for a hub-managed project. // It creates the workspace directory and seeds the .scion project structure with -// hub connection settings. Unlike regular projects, hub-native projects store +// hub connection settings. Unlike regular projects, hub-managed projects store // settings directly in the .scion directory (no split storage or marker files). -func (s *Server) initHubNativeProject(project *store.Project) error { - workspacePath, err := hubNativeProjectPath(project.Slug) +func (s *Server) initHubManagedProject(project *store.Project) error { + workspacePath, err := hubManagedProjectPath(project.Slug) if err != nil { return err } @@ -3788,7 +3788,7 @@ func (s *Server) initHubNativeProject(project *store.Project) error { return fmt.Errorf("failed to create .scion directory: %w", err) } - // Seed default settings.yaml directly in scionDir. Hub-native projects + // Seed default settings.yaml directly in scionDir. Hub-managed projects // bypass InitProject (which uses split storage for git repos) and keep // all configuration in-place. settingsPath := filepath.Join(scionDir, "settings.yaml") @@ -3811,7 +3811,7 @@ func (s *Server) initHubNativeProject(project *store.Project) error { } for key, value := range settingsUpdates { if err := config.UpdateSetting(scionDir, key, value, false); err != nil { - slog.Warn("failed to update hub-native project setting", + slog.Warn("failed to update hub-managed project setting", "project_id", project.ID, "key", key, "error", err.Error()) } } @@ -3820,11 +3820,11 @@ func (s *Server) initHubNativeProject(project *store.Project) error { } // cloneSharedWorkspaceProject performs the host-side git clone for a shared-workspace -// git project. It clones the repository into the hub-native workspace path and +// git project. It clones the repository into the hub-managed workspace path and // seeds the .scion project structure on top. If the clone fails, the workspace // directory is cleaned up and an error is returned. func (s *Server) cloneSharedWorkspaceProject(ctx context.Context, project *store.Project) error { - workspacePath, err := hubNativeProjectPath(project.Slug) + workspacePath, err := hubManagedProjectPath(project.Slug) if err != nil { return err } @@ -3956,7 +3956,7 @@ func (s *Server) resolveCloneToken(ctx context.Context, project *store.Project) return "" } -// syncWorkspaceOnStop triggers a best-effort workspace sync-back for hub-native projects +// syncWorkspaceOnStop triggers a best-effort workspace sync-back for hub-managed projects // on remote brokers before the agent is stopped. It uploads the workspace from the // broker to GCS via the control channel, then downloads from GCS to the Hub filesystem. func (s *Server) syncWorkspaceOnStop(ctx context.Context, agent *store.Agent) { @@ -3966,7 +3966,7 @@ func (s *Server) syncWorkspaceOnStop(ctx context.Context, agent *store.Agent) { project, err := s.store.GetProject(ctx, agent.ProjectID) if err != nil || (project.GitRemote != "" && !project.IsSharedWorkspace()) { - return // Not hub-native/shared-workspace or project not found + return // Not hub-managed/shared-workspace or project not found } // Check if broker is co-located (embedded or has local path) @@ -4000,7 +4000,7 @@ func (s *Server) syncWorkspaceOnStop(ctx context.Context, agent *store.Agent) { } // Download from GCS to Hub filesystem - workspacePath, err := hubNativeProjectPath(project.Slug) + workspacePath, err := hubManagedProjectPath(project.Slug) if err != nil { s.agentLifecycleLog.Warn("syncWorkspaceOnStop: failed to get project path", "agent_id", agent.ID, "error", err) return @@ -4173,7 +4173,7 @@ func (s *Server) handleProjectRegister(w http.ResponseWriter, r *http.Request) { // Add as project provider. When the project already existed and the // broker is already a provider, preserve the existing localPath to - // avoid converting a hub-native git project into a linked project. + // avoid converting a hub-managed git project into a linked project. localPath := req.Path if !created { if existingProvider, err := s.store.GetProjectProvider(ctx, project.ID, broker.ID); err == nil { @@ -4269,7 +4269,7 @@ func (s *Server) handleProjectRegister(w http.ResponseWriter, r *http.Request) { // Add as project provider. When the project already existed and the // broker is already a provider, preserve the existing localPath to - // avoid converting a hub-native git project into a linked project. + // avoid converting a hub-managed git project into a linked project. localPath := req.Path if !created { if existingProvider, err := s.store.GetProjectProvider(ctx, project.ID, broker.ID); err == nil { @@ -5210,7 +5210,7 @@ func (s *Server) deleteProject(w http.ResponseWriter, r *http.Request, id string // Clean up project-scoped harness configs (best-effort), including storage files. s.deleteProjectHarnessConfigs(ctx, id) - // For hub-native and shared-workspace projects, notify provider brokers to clean up + // For hub-managed and shared-workspace projects, notify provider brokers to clean up // their local project directories. This must run before DeleteProject because // the cascade deletes the project_providers we need to enumerate. if project.GitRemote == "" || project.IsSharedWorkspace() { @@ -5222,11 +5222,11 @@ func (s *Server) deleteProject(w http.ResponseWriter, r *http.Request, id string return } - // For hub-native and shared-workspace projects, remove the filesystem directory. + // For hub-managed and shared-workspace projects, remove the filesystem directory. if (project.GitRemote == "" || project.IsSharedWorkspace()) && project.Slug != "" { - if projectPath, err := hubNativeProjectPath(project.Slug); err == nil { + if projectPath, err := hubManagedProjectPath(project.Slug); err == nil { if err := util.RemoveAllSafe(projectPath); err != nil { - slog.Warn("failed to remove hub-native project directory", + slog.Warn("failed to remove hub-managed project directory", "project_id", id, "slug", project.Slug, "path", projectPath, "error", err) } } @@ -5362,7 +5362,7 @@ func (s *Server) deleteProjectHarnessConfigs(ctx context.Context, projectID stri } // cleanupBrokerProjectDirectories notifies provider brokers to remove their local -// copies of a hub-native project directory. This is best-effort: failures are +// copies of a hub-managed project directory. This is best-effort: failures are // logged but do not block project deletion. The embedded broker is skipped // because the hub already cleans up its own filesystem copy. func (s *Server) cleanupBrokerProjectDirectories(ctx context.Context, project *store.Project) { @@ -8618,9 +8618,9 @@ func (s *Server) populateAgentConfig(agent *store.Agent, project *store.Project, } } - // Populate workspace path for hub-native projects and shared-workspace git projects. + // Populate workspace path for hub-managed projects and shared-workspace git projects. if project != nil && (project.GitRemote == "" || project.IsSharedWorkspace()) { - workspacePath, err := hubNativeProjectPath(project.Slug) + workspacePath, err := hubManagedProjectPath(project.Slug) if err == nil { agent.AppliedConfig.Workspace = workspacePath } @@ -8959,7 +8959,7 @@ func (s *Server) resolveRuntimeBroker(ctx context.Context, w http.ResponseWriter "totalProviders", len(allProviders), "onlineProviders", len(availableBrokers), "defaultBroker", project.DefaultRuntimeBrokerID, - "isHubNative", project.GitRemote == "") + "isHubManaged", project.GitRemote == "") // Convert to summary for error responses, marking and prioritizing the default broker brokerSummaries := make([]RuntimeBrokerSummary, 0, len(availableBrokers)) @@ -8998,7 +8998,7 @@ func (s *Server) resolveRuntimeBroker(ctx context.Context, w http.ResponseWriter // Broker is not yet a provider — try to auto-link it. // The user explicitly selected this broker, so we honor that by linking it - // to the project as a provider. This is common for hub-native projects where + // to the project as a provider. This is common for hub-managed projects where // providers aren't established via CLI registration. broker, err := s.findBrokerByIDOrSlug(ctx, requestedBrokerID) if err == nil && broker != nil { diff --git a/pkg/hub/handlers_project_test.go b/pkg/hub/handlers_project_test.go index 5f78a6685..d8408d6dd 100644 --- a/pkg/hub/handlers_project_test.go +++ b/pkg/hub/handlers_project_test.go @@ -37,8 +37,8 @@ import ( "github.com/stretchr/testify/require" ) -func TestHubNativeProjectPath(t *testing.T) { - path, err := hubNativeProjectPath("my-test-project") +func TestHubManagedProjectPath(t *testing.T) { + path, err := hubManagedProjectPath("my-test-project") require.NoError(t, err) homeDir, err := os.UserHomeDir() @@ -48,7 +48,7 @@ func TestHubNativeProjectPath(t *testing.T) { assert.Equal(t, expected, path) } -func TestHubNativeProjectPath_EmptyProjectsFallsBackToGroves(t *testing.T) { +func TestHubManagedProjectPath_EmptyProjectsFallsBackToGroves(t *testing.T) { // Use a temp directory as HOME to avoid polluting real ~/.scion tmpHome := t.TempDir() t.Setenv("HOME", tmpHome) @@ -66,17 +66,17 @@ func TestHubNativeProjectPath_EmptyProjectsFallsBackToGroves(t *testing.T) { require.NoError(t, os.MkdirAll(grovesDir, 0755)) require.NoError(t, os.WriteFile(filepath.Join(grovesDir, "README.md"), []byte("# workspace"), 0644)) - // hubNativeProjectPath should fall back to groves/ since projects/ has no real content - path, err := hubNativeProjectPath(slug) + // hubManagedProjectPath should fall back to groves/ since projects/ has no real content + path, err := hubManagedProjectPath(slug) require.NoError(t, err) assert.Equal(t, grovesDir, path, "should fall back to groves path when projects dir only contains infrastructure dirs") } -func TestCreateProject_HubNative_NoGitRemote(t *testing.T) { +func TestCreateProject_HubManaged_NoGitRemote(t *testing.T) { srv, _ := testServer(t) body := CreateProjectRequest{ - Name: "Hub Native Project", + Name: "Hub Managed Project", } rec := doRequest(t, srv, http.MethodPost, "/api/v1/projects", body) @@ -85,19 +85,19 @@ func TestCreateProject_HubNative_NoGitRemote(t *testing.T) { var project store.Project require.NoError(t, json.NewDecoder(rec.Body).Decode(&project)) - assert.Equal(t, "Hub Native Project", project.Name) - assert.Equal(t, "hub-native-project", project.Slug) - assert.Empty(t, project.GitRemote, "hub-native project should have no git remote") + assert.Equal(t, "Hub Managed Project", project.Name) + assert.Equal(t, "hub-managed-project", project.Slug) + assert.Empty(t, project.GitRemote, "hub-managed project should have no git remote") // Verify the filesystem was initialized - workspacePath, err := hubNativeProjectPath(project.Slug) + workspacePath, err := hubManagedProjectPath(project.Slug) require.NoError(t, err) scionDir := filepath.Join(workspacePath, ".scion") settingsPath := filepath.Join(scionDir, "settings.yaml") _, err = os.Stat(settingsPath) - assert.NoError(t, err, "settings.yaml should exist for hub-native project") + assert.NoError(t, err, "settings.yaml should exist for hub-managed project") // Cleanup t.Cleanup(func() { @@ -122,21 +122,21 @@ func TestCreateProject_GitBacked_NoFilesystemInit(t *testing.T) { assert.Equal(t, "github.com/test/repo", project.GitRemote) // Verify no filesystem was created for git-backed project - workspacePath, err := hubNativeProjectPath(project.Slug) + workspacePath, err := hubManagedProjectPath(project.Slug) require.NoError(t, err) _, err = os.Stat(workspacePath) assert.True(t, os.IsNotExist(err), "no workspace directory should be created for git-backed projects") } -func TestPopulateAgentConfig_HubNativeProject_SetsWorkspace(t *testing.T) { +func TestPopulateAgentConfig_HubManagedProject_SetsWorkspace(t *testing.T) { srv, _ := testServer(t) project := &store.Project{ - ID: "project-hub-native", - Name: "Hub Native", - Slug: "hub-native", - // No GitRemote — hub-native project + ID: "project-hub-managed", + Name: "Hub Managed", + Slug: "hub-managed", + // No GitRemote — hub-managed project } agent := &store.Agent{ @@ -146,22 +146,22 @@ func TestPopulateAgentConfig_HubNativeProject_SetsWorkspace(t *testing.T) { srv.populateAgentConfig(agent, project, nil) - expectedPath, err := hubNativeProjectPath("hub-native") + expectedPath, err := hubManagedProjectPath("hub-managed") require.NoError(t, err) assert.Equal(t, expectedPath, agent.AppliedConfig.Workspace, - "Workspace should be set for hub-native projects") + "Workspace should be set for hub-managed projects") assert.Nil(t, agent.AppliedConfig.GitClone, - "GitClone should not be set for hub-native projects") + "GitClone should not be set for hub-managed projects") } -func TestPopulateAgentConfig_HubNativeProject_RemoteBroker_WorkspaceSet(t *testing.T) { +func TestPopulateAgentConfig_HubManagedProject_RemoteBroker_WorkspaceSet(t *testing.T) { srv, _ := testServer(t) project := &store.Project{ - ID: "project-hub-native-remote", + ID: "project-hub-managed-remote", Name: "Hub Native Remote", - Slug: "hub-native-remote", - // No GitRemote — hub-native project + Slug: "hub-managed-remote", + // No GitRemote — hub-managed project } agent := &store.Agent{ @@ -171,10 +171,10 @@ func TestPopulateAgentConfig_HubNativeProject_RemoteBroker_WorkspaceSet(t *testi srv.populateAgentConfig(agent, project, nil) - // populateAgentConfig sets Workspace for hub-native projects. + // populateAgentConfig sets Workspace for hub-managed projects. // For remote brokers, the createAgent handler later swaps this to // WorkspaceStoragePath. Here we verify the initial workspace is set. - expectedPath, err := hubNativeProjectPath("hub-native-remote") + expectedPath, err := hubManagedProjectPath("hub-managed-remote") require.NoError(t, err) assert.Equal(t, expectedPath, agent.AppliedConfig.Workspace) } @@ -432,10 +432,10 @@ func TestPopulateAgentConfig_ProjectTelemetryEnabledWithoutOtherConfig(t *testin "Project TelemetryEnabled=true should create telemetry config with Enabled=true") } -// TestCreateAgent_HubNativeProject_ExplicitBroker_AutoLinks tests that creating an agent -// in a hub-native project with an explicitly selected broker auto-links the broker as a +// TestCreateAgent_HubManagedProject_ExplicitBroker_AutoLinks tests that creating an agent +// in a hub-managed project with an explicitly selected broker auto-links the broker as a // provider, even if it wasn't previously registered as one. -func TestCreateAgent_HubNativeProject_ExplicitBroker_AutoLinks(t *testing.T) { +func TestCreateAgent_HubManagedProject_ExplicitBroker_AutoLinks(t *testing.T) { srv, s := testServer(t) ctx := context.Background() @@ -448,12 +448,12 @@ func TestCreateAgent_HubNativeProject_ExplicitBroker_AutoLinks(t *testing.T) { } require.NoError(t, s.CreateRuntimeBroker(ctx, broker)) - // Create a hub-native project (no git remote, no default broker, no providers) + // Create a hub-managed project (no git remote, no default broker, no providers) project := &store.Project{ ID: "project-hub-autolink", Slug: "hub-autolink", Name: "Hub Autolink Project", - // No GitRemote — hub-native + // No GitRemote — hub-managed // No DefaultRuntimeBrokerID } require.NoError(t, s.CreateProject(ctx, project)) @@ -488,9 +488,9 @@ func TestCreateAgent_HubNativeProject_ExplicitBroker_AutoLinks(t *testing.T) { "Broker should be set as the default for the project") } -// TestCreateProject_HubNative_AutoProvide tests that creating a hub-native project +// TestCreateProject_HubManaged_AutoProvide tests that creating a hub-managed project // auto-links brokers with auto_provide enabled. -func TestCreateProject_HubNative_AutoProvide(t *testing.T) { +func TestCreateProject_HubManaged_AutoProvide(t *testing.T) { srv, s := testServer(t) ctx := context.Background() @@ -504,7 +504,7 @@ func TestCreateProject_HubNative_AutoProvide(t *testing.T) { } require.NoError(t, s.CreateRuntimeBroker(ctx, broker)) - // Create a hub-native project via the API + // Create a hub-managed project via the API body := CreateProjectRequest{ Name: "Auto Provide Project", } @@ -514,7 +514,7 @@ func TestCreateProject_HubNative_AutoProvide(t *testing.T) { var project store.Project require.NoError(t, json.NewDecoder(rec.Body).Decode(&project)) - assert.Empty(t, project.GitRemote, "should be hub-native") + assert.Empty(t, project.GitRemote, "should be hub-managed") // Verify the auto-provide broker was linked provider, err := s.GetProjectProvider(ctx, project.ID, broker.ID) @@ -540,20 +540,20 @@ func TestCreateProject_HubNative_AutoProvide(t *testing.T) { assert.Equal(t, broker.ID, resp.Agent.RuntimeBrokerID, "Agent should use the auto-provided default broker") - // Cleanup hub-native project filesystem - workspacePath, err := hubNativeProjectPath(project.Slug) + // Cleanup hub-managed project filesystem + workspacePath, err := hubManagedProjectPath(project.Slug) if err == nil { t.Cleanup(func() { os.RemoveAll(workspacePath) }) } } -// TestCreateAgent_HubNativeProject_NoProviders_NoBroker tests that creating an agent -// in a hub-native project with no providers and no explicit broker returns an appropriate error. -func TestDeleteProject_HubNative_RemovesFilesystem(t *testing.T) { +// TestCreateAgent_HubManagedProject_NoProviders_NoBroker tests that creating an agent +// in a hub-managed project with no providers and no explicit broker returns an appropriate error. +func TestDeleteProject_HubManaged_RemovesFilesystem(t *testing.T) { srv, s := testServer(t) - // Create a hub-native project via the API (initializes filesystem) - project, workspacePath := createTestHubNativeProject(t, srv, "FS Delete Test") + // Create a hub-managed project via the API (initializes filesystem) + project, workspacePath := createTestHubManagedProject(t, srv, "FS Delete Test") // Verify filesystem exists before deletion _, err := os.Stat(workspacePath) @@ -652,11 +652,11 @@ func TestDeleteProject_AlwaysCascadeDeletesAgents(t *testing.T) { assert.ErrorIs(t, err, store.ErrNotFound) } -func TestCreateAgent_HubNativeProject_NoProviders_NoBroker(t *testing.T) { +func TestCreateAgent_HubManagedProject_NoProviders_NoBroker(t *testing.T) { srv, s := testServer(t) ctx := context.Background() - // Create a hub-native project with no providers + // Create a hub-managed project with no providers project := &store.Project{ ID: "project-hub-noproviders", Slug: "hub-noproviders", @@ -675,11 +675,11 @@ func TestCreateAgent_HubNativeProject_NoProviders_NoBroker(t *testing.T) { "Should fail when no providers exist and no broker is specified") } -// TestAutoLinkProviders_HubNativeProject_NoLocalPath verifies that autoLinkProviders -// does NOT set LocalPath on the provider for hub-native projects. The hub's local +// TestAutoLinkProviders_HubManagedProject_NoLocalPath verifies that autoLinkProviders +// does NOT set LocalPath on the provider for hub-managed projects. The hub's local // path is not valid for remote brokers — instead, projectSlug is sent so each // broker resolves the path on its own filesystem. -func TestAutoLinkProviders_HubNativeProject_NoLocalPath(t *testing.T) { +func TestAutoLinkProviders_HubManagedProject_NoLocalPath(t *testing.T) { srv, s := testServer(t) ctx := context.Background() @@ -693,7 +693,7 @@ func TestAutoLinkProviders_HubNativeProject_NoLocalPath(t *testing.T) { } require.NoError(t, s.CreateRuntimeBroker(ctx, broker)) - // Create a hub-native project via the API — this triggers autoLinkProviders + // Create a hub-managed project via the API — this triggers autoLinkProviders body := CreateProjectRequest{ Name: "LocalPath Auto Project", } @@ -703,17 +703,17 @@ func TestAutoLinkProviders_HubNativeProject_NoLocalPath(t *testing.T) { var project store.Project require.NoError(t, json.NewDecoder(rec.Body).Decode(&project)) - assert.Empty(t, project.GitRemote, "should be hub-native") + assert.Empty(t, project.GitRemote, "should be hub-managed") // Verify the auto-linked provider does NOT have LocalPath set provider, err := s.GetProjectProvider(ctx, project.ID, broker.ID) require.NoError(t, err, "Auto-provide broker should be linked as a provider") assert.Equal(t, "auto-provide", provider.LinkedBy) assert.Empty(t, provider.LocalPath, - "LocalPath should NOT be set for hub-native project auto-linked provider") + "LocalPath should NOT be set for hub-managed project auto-linked provider") - // Cleanup hub-native project filesystem - workspacePath, err := hubNativeProjectPath(project.Slug) + // Cleanup hub-managed project filesystem + workspacePath, err := hubManagedProjectPath(project.Slug) if err == nil { t.Cleanup(func() { os.RemoveAll(workspacePath) }) } @@ -754,19 +754,19 @@ func TestAutoLinkProviders_GitProject_NoLocalPath(t *testing.T) { "LocalPath should NOT be set for git-backed project providers") } -// TestDeleteProject_HubNative_DispatchesCleanupToBrokers verifies that deleting a -// hub-native project dispatches CleanupProject to each provider broker (except the +// TestDeleteProject_HubManaged_DispatchesCleanupToBrokers verifies that deleting a +// hub-managed project dispatches CleanupProject to each provider broker (except the // embedded/co-located broker). -func TestDeleteProject_HubNative_DispatchesCleanupToBrokers(t *testing.T) { +func TestDeleteProject_HubManaged_DispatchesCleanupToBrokers(t *testing.T) { srv, s := testServer(t) ctx := context.Background() - // Create a hub-native project + // Create a hub-managed project project := &store.Project{ ID: "project-cleanup-dispatch", Slug: "cleanup-dispatch", Name: "Cleanup Dispatch Project", - // No GitRemote — hub-native + // No GitRemote — hub-managed } require.NoError(t, s.CreateProject(ctx, project)) @@ -818,13 +818,13 @@ func TestDeleteProject_HubNative_DispatchesCleanupToBrokers(t *testing.T) { assert.ErrorIs(t, err, store.ErrNotFound) } -// TestDeleteProject_HubNative_SkipsEmbeddedBroker verifies that the embedded broker +// TestDeleteProject_HubManaged_SkipsEmbeddedBroker verifies that the embedded broker // (co-located hub+broker) is not called for cleanup since the hub handles its own copy. -func TestDeleteProject_HubNative_SkipsEmbeddedBroker(t *testing.T) { +func TestDeleteProject_HubManaged_SkipsEmbeddedBroker(t *testing.T) { srv, s := testServer(t) ctx := context.Background() - // Create a hub-native project + // Create a hub-managed project project := &store.Project{ ID: "project-cleanup-embedded", Slug: "cleanup-embedded", @@ -922,10 +922,10 @@ func TestDeleteProject_GitBacked_NoCleanupDispatched(t *testing.T) { assert.Equal(t, 0, mockClient.cleanupCalls, "CleanupProject should not be called for git-backed projects") } -// TestResolveRuntimeBroker_HubNativeProject_NoLocalPath verifies that when a broker -// is auto-linked during agent creation for a hub-native project, LocalPath is NOT +// TestResolveRuntimeBroker_HubManagedProject_NoLocalPath verifies that when a broker +// is auto-linked during agent creation for a hub-managed project, LocalPath is NOT // set. Remote brokers resolve the path themselves via projectSlug. -func TestResolveRuntimeBroker_HubNativeProject_NoLocalPath(t *testing.T) { +func TestResolveRuntimeBroker_HubManagedProject_NoLocalPath(t *testing.T) { srv, s := testServer(t) ctx := context.Background() @@ -938,7 +938,7 @@ func TestResolveRuntimeBroker_HubNativeProject_NoLocalPath(t *testing.T) { } require.NoError(t, s.CreateRuntimeBroker(ctx, broker)) - // Create a hub-native project with no providers + // Create a hub-managed project with no providers project := &store.Project{ ID: "project-resolve-localpath", Slug: "resolve-localpath", @@ -961,12 +961,12 @@ func TestResolveRuntimeBroker_HubNativeProject_NoLocalPath(t *testing.T) { require.NoError(t, err, "Broker should have been auto-linked") assert.Equal(t, "agent-create", provider.LinkedBy) assert.Empty(t, provider.LocalPath, - "LocalPath should NOT be set when auto-linking during agent creation for hub-native project") + "LocalPath should NOT be set when auto-linking during agent creation for hub-managed project") } // TestProjectRegisterPreservesProviderLocalPath verifies that re-registering a // project from a local checkout does not overwrite an existing provider's empty -// localPath. This prevents a hub-native git project (where agents clone from a +// localPath. This prevents a hub-managed git project (where agents clone from a // URL) from being accidentally converted into a linked project. func TestProjectRegisterPreservesProviderLocalPath(t *testing.T) { srv, s := testServer(t) @@ -981,7 +981,7 @@ func TestProjectRegisterPreservesProviderLocalPath(t *testing.T) { } require.NoError(t, s.CreateRuntimeBroker(ctx, broker)) - // Step 1: Register project (creates it) — this is the initial hub-native creation. + // Step 1: Register project (creates it) — this is the initial hub-managed creation. // The broker is linked WITH a localPath (simulating CLI-initiated creation). body := map[string]interface{}{ "name": "preserve-path-project", @@ -1003,7 +1003,7 @@ func TestProjectRegisterPreservesProviderLocalPath(t *testing.T) { assert.Equal(t, "/original/path/.scion", provider.LocalPath, "newly created project should have localPath from registration") - // Now simulate converting to hub-native: clear localPath directly + // Now simulate converting to hub-managed: clear localPath directly // (as autoLinkProviders would do, or via admin action) require.NoError(t, s.AddProjectProvider(ctx, &store.ProjectProvider{ ProjectID: projectID, @@ -1011,7 +1011,7 @@ func TestProjectRegisterPreservesProviderLocalPath(t *testing.T) { BrokerName: broker.Name, Status: store.BrokerStatusOnline, LinkedBy: "auto-provide", - // LocalPath intentionally empty — hub-native provider + // LocalPath intentionally empty — hub-managed provider })) // Verify localPath is now empty @@ -1366,7 +1366,7 @@ func TestCreateProject_SharedWorkspace_SetsLabelAndInitFilesystem(t *testing.T) assert.True(t, project.IsSharedWorkspace(), "project should report as shared workspace") // Verify workspace was cloned (it's a git repo) - workspacePath, err := hubNativeProjectPath(project.Slug) + workspacePath, err := hubManagedProjectPath(project.Slug) require.NoError(t, err) t.Cleanup(func() { os.RemoveAll(workspacePath) }) @@ -1417,7 +1417,7 @@ func TestPopulateAgentConfig_SharedWorkspace_SetsWorkspaceNotClone(t *testing.T) srv.populateAgentConfig(agent, project, nil) - expectedPath, err := hubNativeProjectPath("shared-ws") + expectedPath, err := hubManagedProjectPath("shared-ws") require.NoError(t, err) assert.Equal(t, expectedPath, agent.AppliedConfig.Workspace, "Workspace should be set for shared-workspace git projects") @@ -1515,7 +1515,7 @@ func TestCloneSharedWorkspaceProject_Success(t *testing.T) { require.NoError(t, err) // Verify the workspace was created with a git repo - workspacePath, err := hubNativeProjectPath(project.Slug) + workspacePath, err := hubManagedProjectPath(project.Slug) require.NoError(t, err) t.Cleanup(func() { os.RemoveAll(workspacePath) }) @@ -1551,7 +1551,7 @@ func TestCloneSharedWorkspaceProject_Failure_CleansUp(t *testing.T) { assert.Contains(t, err.Error(), "shared workspace clone failed") // Verify the workspace directory was cleaned up - workspacePath, pathErr := hubNativeProjectPath(project.Slug) + workspacePath, pathErr := hubManagedProjectPath(project.Slug) require.NoError(t, pathErr) _, statErr := os.Stat(workspacePath) assert.True(t, os.IsNotExist(statErr), "workspace directory should be cleaned up on clone failure") @@ -1700,7 +1700,7 @@ func TestCreateProject_AutoAssociatesGitHubInstallation(t *testing.T) { require.NoError(t, json.NewDecoder(rec.Body).Decode(&project)) // Clean up the cloned workspace - workspacePath, err := hubNativeProjectPath(project.Slug) + workspacePath, err := hubManagedProjectPath(project.Slug) require.NoError(t, err) t.Cleanup(func() { os.RemoveAll(workspacePath) }) diff --git a/pkg/hub/httpdispatcher.go b/pkg/hub/httpdispatcher.go index 85d682a41..e27876ab1 100644 --- a/pkg/hub/httpdispatcher.go +++ b/pkg/hub/httpdispatcher.go @@ -282,7 +282,7 @@ func (d *HTTPAgentDispatcher) buildCreateRequest(ctx context.Context, agent *sto workspace := agent.AppliedConfig.Workspace gitClone := agent.AppliedConfig.GitClone // When the broker has a local provider path for this project, clear - // the hub-native workspace path — the broker will derive its own + // the hub-managed workspace path — the broker will derive its own // workspace location from the project path. However, keep GitClone // config: all hub-linked projects with a git remote use clone-based // provisioning (HTTPS + GitHub token) rather than worktree-based, @@ -502,7 +502,7 @@ func (d *HTTPAgentDispatcher) resolveDispatchProjectPath(ctx context.Context, ag func (d *HTTPAgentDispatcher) resolveDispatchProjectInfo(ctx context.Context, agent *store.Agent) projectDispatchInfo { // Look up the local path for this project on the target runtime broker. - // A provider LocalPath (linked project) takes precedence over hub-native + // A provider LocalPath (linked project) takes precedence over hub-managed // slug resolution, even for projects without a git remote. Only when there // is no provider path and no git remote do we fall back to projectSlug so // the broker resolves the conventional ~/.scion/projects/ path. @@ -535,7 +535,7 @@ func (d *HTTPAgentDispatcher) resolveDispatchProjectInfo(ctx context.Context, ag } } // If no provider path was found, let the broker resolve the path via - // slug. This applies to both hub-native projects (no git remote) and + // slug. This applies to both hub-managed projects (no git remote) and // git-anchored projects — the broker needs a project identity to create // agent directories under ~/.scion/projects// rather than falling // back to the global project. diff --git a/pkg/hub/httpdispatcher_test.go b/pkg/hub/httpdispatcher_test.go index dd7c1d9a1..5cfa7b46d 100644 --- a/pkg/hub/httpdispatcher_test.go +++ b/pkg/hub/httpdispatcher_test.go @@ -537,7 +537,7 @@ func TestHTTPAgentDispatcher_DispatchAgentCreate_WithProjectProviderPath(t *test memStore := createTestStore(t) // Create the project with a GitRemote so it is treated as a linked project - // (not hub-native). This ensures buildCreateRequest looks up the + // (not hub-managed). This ensures buildCreateRequest looks up the // provider's LocalPath instead of sending a projectSlug. project := &store.Project{ ID: "project-1", @@ -1171,16 +1171,16 @@ func TestHTTPAgentDispatcher_DispatchAgentStart_IncludesAgentIdentity(t *testing } } -func TestHTTPAgentDispatcher_DispatchAgentStart_HubNativeProject(t *testing.T) { +func TestHTTPAgentDispatcher_DispatchAgentStart_HubManagedProject(t *testing.T) { ctx := context.Background() memStore := createTestStore(t) - // Create a hub-native project (no git remote) + // Create a hub-managed project (no git remote) project := &store.Project{ ID: "project-hub", Name: "My Hub Project", Slug: "my-hub-project", - // No GitRemote — this is a hub-native project + // No GitRemote — this is a hub-managed project } if err := memStore.CreateProject(ctx, project); err != nil { t.Fatalf("failed to create project: %v", err) @@ -1219,7 +1219,7 @@ func TestHTTPAgentDispatcher_DispatchAgentStart_HubNativeProject(t *testing.T) { } // No local provider path — projectPath should be empty if mockClient.lastProjectPath != "" { - t.Errorf("expected empty projectPath for hub-native project, got %q", mockClient.lastProjectPath) + t.Errorf("expected empty projectPath for hub-managed project, got %q", mockClient.lastProjectPath) } // ProjectSlug should be set so the broker can resolve the path if mockClient.lastProjectSlug != "my-hub-project" { @@ -1830,16 +1830,16 @@ func TestHTTPAgentDispatcher_DispatchAgentCreate_PropagatesProfile(t *testing.T) } } -func TestHTTPAgentDispatcher_DispatchAgentCreate_PropagatesProjectSlug_HubNative(t *testing.T) { +func TestHTTPAgentDispatcher_DispatchAgentCreate_PropagatesProjectSlug_HubManaged(t *testing.T) { ctx := context.Background() memStore := createTestStore(t) - // Create a hub-native project (no GitRemote) + // Create a hub-managed project (no GitRemote) project := &store.Project{ - ID: "project-hub-native", - Name: "Hub Native Project", - Slug: "hub-native-project", - // No GitRemote = hub-native + ID: "project-hub-managed", + Name: "Hub Managed Project", + Slug: "hub-managed-project", + // No GitRemote = hub-managed } if err := memStore.CreateProject(ctx, project); err != nil { t.Fatalf("failed to create project: %v", err) @@ -1863,7 +1863,7 @@ func TestHTTPAgentDispatcher_DispatchAgentCreate_PropagatesProjectSlug_HubNative ID: "agent-1", Name: "test-agent", Slug: "test-agent", - ProjectID: "project-hub-native", + ProjectID: "project-hub-managed", RuntimeBrokerID: "host-1", AppliedConfig: &store.AgentAppliedConfig{ HarnessConfig: "claude", @@ -1878,8 +1878,8 @@ func TestHTTPAgentDispatcher_DispatchAgentCreate_PropagatesProjectSlug_HubNative if !mockClient.createCalled { t.Fatal("expected CreateAgent to be called") } - if mockClient.lastCreateReq.ProjectSlug != "hub-native-project" { - t.Errorf("expected ProjectSlug 'hub-native-project', got '%s'", mockClient.lastCreateReq.ProjectSlug) + if mockClient.lastCreateReq.ProjectSlug != "hub-managed-project" { + t.Errorf("expected ProjectSlug 'hub-managed-project', got '%s'", mockClient.lastCreateReq.ProjectSlug) } } @@ -1989,7 +1989,7 @@ func TestHTTPAgentDispatcher_DispatchAgentCreate_NoProjectSlug_LocalPathProject( memStore := createTestStore(t) // Create a linked project with a local provider path. - // This project has a GitRemote so it is treated as a linked project (not hub-native). + // This project has a GitRemote so it is treated as a linked project (not hub-managed). // Even though the broker has the repo locally, all hub-linked projects with a // git remote use clone-based provisioning (HTTPS + GitHub token). project := &store.Project{ @@ -2059,7 +2059,7 @@ func TestHTTPAgentDispatcher_DispatchAgentCreate_NoProjectSlug_LocalPathProject( } // A non-git project with a local provider path should NOT have ProjectSlug set. - // ProjectSlug is only for hub-native projects (no local path on the broker). + // ProjectSlug is only for hub-managed projects (no local path on the broker). if mockClient.lastCreateReq.ProjectSlug != "" { t.Errorf("expected empty ProjectSlug for local-path project, got '%s'", mockClient.lastCreateReq.ProjectSlug) } @@ -2070,7 +2070,7 @@ func TestHTTPAgentDispatcher_DispatchAgentCreate_NoProjectSlug_LocalPathProject( } // Config.Workspace should be cleared when a local provider path exists, - // because the workspace is derived from the project path, not the hub-native convention. + // because the workspace is derived from the project path, not the hub-managed convention. if mockClient.lastCreateReq.Config == nil { t.Fatal("expected config to be present") } @@ -2091,7 +2091,7 @@ func TestHTTPAgentDispatcher_DispatchAgentCreate_NoProjectSlug_LocalPathProject( // TestHTTPAgentDispatcher_DispatchAgentCreate_LinkedProjectNoGitRemote verifies // that a linked project without a git remote (registered via CLI link, not via -// git URL) uses the provider's LocalPath rather than being treated as hub-native. +// git URL) uses the provider's LocalPath rather than being treated as hub-managed. func TestHTTPAgentDispatcher_DispatchAgentCreate_LinkedProjectNoGitRemote(t *testing.T) { ctx := context.Background() memStore := createTestStore(t) @@ -2102,7 +2102,7 @@ func TestHTTPAgentDispatcher_DispatchAgentCreate_LinkedProjectNoGitRemote(t *tes ID: "project-linked-no-git", Name: "Linked No Git Project", Slug: "linked-no-git", - // No GitRemote — looks like hub-native, but has a provider path + // No GitRemote — looks like hub-managed, but has a provider path } if err := memStore.CreateProject(ctx, project); err != nil { t.Fatalf("failed to create project: %v", err) @@ -2155,7 +2155,7 @@ func TestHTTPAgentDispatcher_DispatchAgentCreate_LinkedProjectNoGitRemote(t *tes t.Fatal("expected CreateAgent to be called") } - // Provider path must take precedence — should NOT be treated as hub-native + // Provider path must take precedence — should NOT be treated as hub-managed if mockClient.lastCreateReq.ProjectSlug != "" { t.Errorf("expected empty ProjectSlug for linked project with provider path, got '%s'", mockClient.lastCreateReq.ProjectSlug) } diff --git a/pkg/hub/project_cache.go b/pkg/hub/project_cache.go index f819b1178..874664789 100644 --- a/pkg/hub/project_cache.go +++ b/pkg/hub/project_cache.go @@ -168,7 +168,7 @@ func (s *Server) handleProjectCacheRefresh(w http.ResponseWriter, r *http.Reques return } - // Hub-native projects don't need cache refresh — they are the source of truth + // Hub-managed projects don't need cache refresh — they are the source of truth if project.GitRemote == "" && !s.isLinkedProject(ctx, project) { Conflict(w, "Cache refresh is only applicable to linked projects with remote workspaces") return @@ -215,7 +215,7 @@ func (s *Server) handleProjectCacheStatus(w http.ResponseWriter, r *http.Request } // Check if a cache exists on disk - cachePath, err := hubNativeProjectPath(project.Slug) + cachePath, err := hubManagedProjectPath(project.Slug) if err != nil { InternalError(w) return @@ -274,7 +274,7 @@ func (s *Server) handleProjectCacheNotify(w http.ResponseWriter, r *http.Request } // Download the latest workspace from GCS to local cache - cachePath, err := hubNativeProjectPath(project.Slug) + cachePath, err := hubManagedProjectPath(project.Slug) if err != nil { InternalError(w) return @@ -363,7 +363,7 @@ func (s *Server) refreshProjectCacheFromBroker(ctx context.Context, project *sto } // Download from GCS to local cache - cachePath, err := hubNativeProjectPath(project.Slug) + cachePath, err := hubManagedProjectPath(project.Slug) if err != nil { return nil, fmt.Errorf("failed to resolve cache path: %w", err) } @@ -459,7 +459,7 @@ func (s *Server) findConnectedProvider(ctx context.Context, project *store.Proje // hasProjectCache returns true if the hub has a cached copy of the project workspace. func hasProjectCache(slug string) bool { - cachePath, err := hubNativeProjectPath(slug) + cachePath, err := hubManagedProjectPath(slug) if err != nil { return false } diff --git a/pkg/hub/project_cache_test.go b/pkg/hub/project_cache_test.go index f1d75eb7c..0b5c1b1fe 100644 --- a/pkg/hub/project_cache_test.go +++ b/pkg/hub/project_cache_test.go @@ -61,7 +61,7 @@ func createTestLinkedProject(t *testing.T, srv *Server, s store.Store, name, rem })) // Set up the hub-side cache directory - cachePath, err := hubNativeProjectPath(project.Slug) + cachePath, err := hubManagedProjectPath(project.Slug) require.NoError(t, err) t.Cleanup(func() { os.RemoveAll(cachePath) }) @@ -72,9 +72,9 @@ func createTestLinkedProject(t *testing.T, srv *Server, s store.Store, name, rem // resolveProjectWebDAVPath Tests // ============================================================================ -func TestResolveProjectWebDAVPath_HubNativeProject(t *testing.T) { +func TestResolveProjectWebDAVPath_HubManagedProject(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WebDAV HubNative") + project, workspacePath := createTestHubManagedProject(t, srv, "WebDAV HubManaged") path, err := srv.resolveProjectWebDAVPath(context.Background(), project) require.NoError(t, err) @@ -89,7 +89,7 @@ func TestResolveProjectWebDAVPath_LinkedProject_CacheDir(t *testing.T) { path, err := srv.resolveProjectWebDAVPath(context.Background(), project) require.NoError(t, err) - expectedCache, err := hubNativeProjectPath(project.Slug) + expectedCache, err := hubManagedProjectPath(project.Slug) require.NoError(t, err) assert.Equal(t, expectedCache, path) } @@ -125,9 +125,9 @@ func TestResolveProjectWebDAVPath_LinkedProject_EmbeddedBroker(t *testing.T) { // isLinkedProject Tests // ============================================================================ -func TestIsLinkedProject_HubNative(t *testing.T) { +func TestIsLinkedProject_HubManaged(t *testing.T) { srv, _ := testServer(t) - project, _ := createTestHubNativeProject(t, srv, "IsLinked HubNative") + project, _ := createTestHubManagedProject(t, srv, "IsLinked HubManaged") assert.False(t, srv.isLinkedProject(context.Background(), project)) } @@ -182,7 +182,7 @@ func TestProjectCacheStatus_CacheExists(t *testing.T) { project, _ := createTestLinkedProject(t, srv, s, "Cache Status With", "https://github.com/org/cache-with.git") // Create the cache directory with some files - cachePath, err := hubNativeProjectPath(project.Slug) + cachePath, err := hubManagedProjectPath(project.Slug) require.NoError(t, err) require.NoError(t, os.MkdirAll(cachePath, 0755)) require.NoError(t, os.WriteFile(filepath.Join(cachePath, "cached.txt"), []byte("cached"), 0644)) @@ -216,7 +216,7 @@ func TestProjectWebDAV_LinkedProject_ServesFromCache(t *testing.T) { project, _ := createTestLinkedProject(t, srv, s, "WebDAV Linked Serve", "https://github.com/org/dav-linked.git") // Populate the cache directory - cachePath, err := hubNativeProjectPath(project.Slug) + cachePath, err := hubManagedProjectPath(project.Slug) require.NoError(t, err) require.NoError(t, os.MkdirAll(cachePath, 0755)) require.NoError(t, os.WriteFile(filepath.Join(cachePath, "hello.txt"), []byte("hello from cache"), 0644)) @@ -247,7 +247,7 @@ func TestProjectWorkspaceList_LinkedProject(t *testing.T) { project, _ := createTestLinkedProject(t, srv, s, "WS List Linked", "https://github.com/org/ws-list.git") // Populate the cache - cachePath, err := hubNativeProjectPath(project.Slug) + cachePath, err := hubManagedProjectPath(project.Slug) require.NoError(t, err) require.NoError(t, os.MkdirAll(cachePath, 0755)) require.NoError(t, os.WriteFile(filepath.Join(cachePath, "file1.txt"), []byte("one"), 0644)) @@ -267,13 +267,13 @@ func TestProjectWorkspaceList_LinkedProject(t *testing.T) { // Cache Refresh Endpoint Tests // ============================================================================ -func TestProjectCacheRefresh_HubNativeProject_Conflict(t *testing.T) { +func TestProjectCacheRefresh_HubManagedProject_Conflict(t *testing.T) { srv, _ := testServer(t) - project, _ := createTestHubNativeProject(t, srv, "Cache Refresh HubNative") + project, _ := createTestHubManagedProject(t, srv, "Cache Refresh HubManaged") rec := doRequest(t, srv, http.MethodPost, fmt.Sprintf("/api/v1/projects/%s/workspace/cache/refresh", project.ID), nil) - // Hub-native projects should return conflict (they don't need cache refresh) + // Hub-managed projects should return conflict (they don't need cache refresh) assert.Equal(t, http.StatusConflict, rec.Code) } diff --git a/pkg/hub/project_webdav.go b/pkg/hub/project_webdav.go index eae9511ec..4513fdfed 100644 --- a/pkg/hub/project_webdav.go +++ b/pkg/hub/project_webdav.go @@ -46,7 +46,7 @@ var syncExcludeExtensions = []string{ // It mounts at /api/v1/projects/{projectId}/dav/ and serves the project's workspace // directory with file exclusion filters applied. // -// For hub-native and shared-workspace projects, it serves the workspace directly. +// For hub-managed and shared-workspace projects, it serves the workspace directly. // For linked projects (workspace on a remote broker), it serves from the hub's // cached copy. The cache is populated via the cache/refresh or cache/notify // endpoints (Phase 3: Linked Project Relay). @@ -118,7 +118,7 @@ func (s *Server) updateProjectSyncState(projectID, workspacePath string) { now := time.Now() state := &store.ProjectSyncState{ ProjectID: projectID, - BrokerID: "", // hub-native + BrokerID: "", // hub-managed LastSyncTime: &now, FileCount: fileCount, TotalBytes: totalBytes, @@ -130,13 +130,13 @@ func (s *Server) updateProjectSyncState(projectID, workspacePath string) { } // resolveProjectWebDAVPath determines the filesystem path to serve via WebDAV -// for a given project. For hub-native and shared-workspace projects, this is the +// for a given project. For hub-managed and shared-workspace projects, this is the // hub-managed workspace directory. For linked projects (workspace on a remote // broker), this is the hub's cached copy of that workspace. func (s *Server) resolveProjectWebDAVPath(ctx context.Context, project *store.Project) (string, error) { - // Hub-native projects (no git remote) always have a managed workspace + // Hub-managed projects (no git remote) always have a managed workspace if project.GitRemote == "" { - path, err := hubNativeProjectPath(project.Slug) + path, err := hubManagedProjectPath(project.Slug) if err != nil { return "", fmt.Errorf("failed to resolve project path") } @@ -145,7 +145,7 @@ func (s *Server) resolveProjectWebDAVPath(ctx context.Context, project *store.Pr // Shared-workspace git projects have a managed workspace on the hub if project.IsSharedWorkspace() { - path, err := hubNativeProjectPath(project.Slug) + path, err := hubManagedProjectPath(project.Slug) if err != nil { return "", fmt.Errorf("failed to resolve project path") } @@ -174,7 +174,7 @@ func (s *Server) resolveProjectWebDAVPath(ctx context.Context, project *store.Pr // Remote linked project: serve from the hub's cached copy. // The cache is populated via cache/refresh or cache/notify endpoints. - cachePath, err := hubNativeProjectPath(project.Slug) + cachePath, err := hubManagedProjectPath(project.Slug) if err != nil { return "", fmt.Errorf("failed to resolve project cache path") } diff --git a/pkg/hub/project_workspace_handlers.go b/pkg/hub/project_workspace_handlers.go index e6a3b889a..ea233e7a5 100644 --- a/pkg/hub/project_workspace_handlers.go +++ b/pkg/hub/project_workspace_handlers.go @@ -218,7 +218,7 @@ func (s *Server) handleProjectWorkspace(w http.ResponseWriter, r *http.Request, return } - // Resolve workspace path — supports hub-native, shared-workspace, and linked projects + // Resolve workspace path — supports hub-managed, shared-workspace, and linked projects workspacePath, err := s.resolveProjectWebDAVPath(ctx, project) if err != nil { Conflict(w, err.Error()) @@ -528,7 +528,7 @@ func (s *Server) handleProjectWorkspaceArchive(w http.ResponseWriter, r *http.Re return } - // Resolve workspace path — supports hub-native, shared-workspace, and linked projects + // Resolve workspace path — supports hub-managed, shared-workspace, and linked projects workspacePath, err := s.resolveProjectWebDAVPath(ctx, project) if err != nil { Conflict(w, err.Error()) @@ -792,10 +792,10 @@ type sharedDirResolution struct { // matching the path used by agent provisioning (config.GetSharedDirPath). // For git-based projects with a co-located broker that has a LocalPath, the path is // resolved via config.GetSharedDirPath(localPath, dirName). Otherwise, the path is -// resolved via the .scion marker in the hub-native workspace directory. +// resolved via the .scion marker in the hub-managed workspace directory. func (s *Server) resolveSharedDirPath(ctx context.Context, project *store.Project, dirName string) (*sharedDirResolution, error) { if project.GitRemote == "" { - // Hub-native project: resolve via the .scion marker in the workspace directory + // Hub-managed project: resolve via the .scion marker in the workspace directory // to find the project-configs path where shared dirs actually live. sdPath, err := resolveHubProjectSharedDirPath(project.Slug, dirName) if err != nil { @@ -858,7 +858,7 @@ func (s *Server) resolveSharedDirPath(ctx context.Context, project *store.Projec // marker (or project-id for git clones) to find the external project-configs path, // then returns the shared-dirs/ subdirectory within it. func resolveHubProjectSharedDirPath(projectSlug, dirName string) (string, error) { - workspacePath, err := hubNativeProjectPath(projectSlug) + workspacePath, err := hubManagedProjectPath(projectSlug) if err != nil { return "", err } @@ -911,7 +911,7 @@ func (s *Server) handleProjectWorkspacePull(w http.ResponseWriter, r *http.Reque return } - workspacePath, err := hubNativeProjectPath(project.Slug) + workspacePath, err := hubManagedProjectPath(project.Slug) if err != nil { InternalError(w) return diff --git a/pkg/hub/project_workspace_handlers_test.go b/pkg/hub/project_workspace_handlers_test.go index ef9f49a30..50068be08 100644 --- a/pkg/hub/project_workspace_handlers_test.go +++ b/pkg/hub/project_workspace_handlers_test.go @@ -63,10 +63,10 @@ func doMultipartRequest(t *testing.T, srv *Server, method, path string, files ma return rec } -// createTestHubNativeProject creates a hub-native project (no git remote) via the API +// createTestHubManagedProject creates a hub-managed project (no git remote) via the API // and returns the project and its workspace path. Cleans up the workspace and any // external project-config directory on test completion. -func createTestHubNativeProject(t *testing.T, srv *Server, name string) (*store.Project, string) { +func createTestHubManagedProject(t *testing.T, srv *Server, name string) (*store.Project, string) { t.Helper() rec := doRequest(t, srv, http.MethodPost, "/api/v1/projects", CreateProjectRequest{Name: name}) @@ -75,7 +75,7 @@ func createTestHubNativeProject(t *testing.T, srv *Server, name string) (*store. var project store.Project require.NoError(t, json.NewDecoder(rec.Body).Decode(&project)) - workspacePath, err := hubNativeProjectPath(project.Slug) + workspacePath, err := hubManagedProjectPath(project.Slug) require.NoError(t, err) t.Cleanup(func() { @@ -94,7 +94,7 @@ func createTestHubNativeProject(t *testing.T, srv *Server, name string) (*store. } // resolveTestSharedDirPath resolves the project-configs shared dir path for a test -// hub-native project. This matches the path that resolveHubProjectSharedDirPath uses +// hub-managed project. This matches the path that resolveHubProjectSharedDirPath uses // in production: it reads the .scion marker to find the project-configs directory. func resolveTestSharedDirPath(t *testing.T, workspacePath, dirName string) string { t.Helper() @@ -127,7 +127,7 @@ func createTestGitProject(t *testing.T, srv *Server, name, remote string) *store func TestProjectWorkspaceList_EmptyWorkspace(t *testing.T) { srv, _ := testServer(t) - project, _ := createTestHubNativeProject(t, srv, "WS List Empty") + project, _ := createTestHubManagedProject(t, srv, "WS List Empty") rec := doRequest(t, srv, http.MethodGet, fmt.Sprintf("/api/v1/projects/%s/workspace/files", project.ID), nil) require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) @@ -143,7 +143,7 @@ func TestProjectWorkspaceList_EmptyWorkspace(t *testing.T) { func TestProjectWorkspaceList_WithFiles(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS List Files") + project, workspacePath := createTestHubManagedProject(t, srv, "WS List Files") // Create some test files require.NoError(t, os.WriteFile(filepath.Join(workspacePath, "hello.txt"), []byte("hello world"), 0644)) @@ -166,7 +166,7 @@ func TestProjectWorkspaceList_WithFiles(t *testing.T) { func TestProjectWorkspaceList_IncludesScionDir(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS List Scion") + project, workspacePath := createTestHubManagedProject(t, srv, "WS List Scion") require.NoError(t, os.WriteFile(filepath.Join(workspacePath, "visible.txt"), []byte("yes"), 0644)) require.NoError(t, os.WriteFile(filepath.Join(workspacePath, ".scion", "extra.txt"), []byte("also visible"), 0644)) @@ -206,7 +206,7 @@ func TestProjectWorkspaceList_GitProjectRejected(t *testing.T) { func TestProjectWorkspaceUpload_SingleFile(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Upload Single") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Upload Single") files := map[string][]byte{ "readme.txt": []byte("hello from upload"), @@ -229,7 +229,7 @@ func TestProjectWorkspaceUpload_SingleFile(t *testing.T) { func TestProjectWorkspaceUpload_MultipleFiles(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Upload Multi") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Upload Multi") files := map[string][]byte{ "a.txt": []byte("file a"), @@ -253,7 +253,7 @@ func TestProjectWorkspaceUpload_MultipleFiles(t *testing.T) { func TestProjectWorkspaceUpload_NestedPath(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Upload Nested") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Upload Nested") files := map[string][]byte{ "src/main.go": []byte("package main"), @@ -269,7 +269,7 @@ func TestProjectWorkspaceUpload_NestedPath(t *testing.T) { func TestProjectWorkspaceUpload_PathTraversalRejected(t *testing.T) { srv, _ := testServer(t) - project, _ := createTestHubNativeProject(t, srv, "WS Upload Traversal") + project, _ := createTestHubManagedProject(t, srv, "WS Upload Traversal") files := map[string][]byte{ "../escape.txt": []byte("bad"), @@ -280,7 +280,7 @@ func TestProjectWorkspaceUpload_PathTraversalRejected(t *testing.T) { func TestProjectWorkspaceUpload_NoFilesRejected(t *testing.T) { srv, _ := testServer(t) - project, _ := createTestHubNativeProject(t, srv, "WS Upload Empty") + project, _ := createTestHubManagedProject(t, srv, "WS Upload Empty") // Send an empty multipart form var buf bytes.Buffer @@ -313,7 +313,7 @@ func TestProjectWorkspaceUpload_GitProjectRejected(t *testing.T) { func TestProjectWorkspaceDelete_Success(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Delete OK") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Delete OK") // Create a file to delete require.NoError(t, os.WriteFile(filepath.Join(workspacePath, "doomed.txt"), []byte("bye"), 0644)) @@ -328,7 +328,7 @@ func TestProjectWorkspaceDelete_Success(t *testing.T) { func TestProjectWorkspaceDelete_NotFound(t *testing.T) { srv, _ := testServer(t) - project, _ := createTestHubNativeProject(t, srv, "WS Delete NF") + project, _ := createTestHubManagedProject(t, srv, "WS Delete NF") rec := doRequest(t, srv, http.MethodDelete, fmt.Sprintf("/api/v1/projects/%s/workspace/files/nonexistent.txt", project.ID), nil) assert.Equal(t, http.StatusNotFound, rec.Code) @@ -336,7 +336,7 @@ func TestProjectWorkspaceDelete_NotFound(t *testing.T) { func TestProjectWorkspaceDelete_CleansEmptyDirs(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Delete Clean") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Delete Clean") // Create a nested file nestedDir := filepath.Join(workspacePath, "deep", "nested") @@ -362,7 +362,7 @@ func TestProjectWorkspaceDelete_CleansEmptyDirs(t *testing.T) { func TestProjectWorkspaceDownload_Success(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Download OK") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Download OK") content := []byte("hello download") require.NoError(t, os.WriteFile(filepath.Join(workspacePath, "readme.txt"), content, 0644)) @@ -377,7 +377,7 @@ func TestProjectWorkspaceDownload_Success(t *testing.T) { func TestProjectWorkspaceDownload_NestedFile(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Download Nested") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Download Nested") require.NoError(t, os.MkdirAll(filepath.Join(workspacePath, "src"), 0755)) require.NoError(t, os.WriteFile(filepath.Join(workspacePath, "src", "main.go"), []byte("package main"), 0644)) @@ -391,7 +391,7 @@ func TestProjectWorkspaceDownload_NestedFile(t *testing.T) { func TestProjectWorkspaceDownload_NotFound(t *testing.T) { srv, _ := testServer(t) - project, _ := createTestHubNativeProject(t, srv, "WS Download NF") + project, _ := createTestHubManagedProject(t, srv, "WS Download NF") rec := doRequest(t, srv, http.MethodGet, fmt.Sprintf("/api/v1/projects/%s/workspace/files/nonexistent.txt", project.ID), nil) assert.Equal(t, http.StatusNotFound, rec.Code) @@ -399,7 +399,7 @@ func TestProjectWorkspaceDownload_NotFound(t *testing.T) { func TestProjectWorkspaceDownload_InlineView(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Download Inline") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Download Inline") require.NoError(t, os.WriteFile(filepath.Join(workspacePath, "readme.txt"), []byte("inline content"), 0644)) @@ -417,7 +417,7 @@ func TestProjectWorkspaceDownload_InlineView(t *testing.T) { func TestProjectWorkspaceDownload_FormatJSON(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Download JSON") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Download JSON") content := "# Hello\n\nThis is markdown." require.NoError(t, os.WriteFile(filepath.Join(workspacePath, "readme.md"), []byte(content), 0644)) @@ -436,7 +436,7 @@ func TestProjectWorkspaceDownload_FormatJSON(t *testing.T) { func TestProjectWorkspaceDownload_FormatJSON_BinaryRejected(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Download JSON Bin") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Download JSON Bin") // Write binary content (invalid UTF-8) require.NoError(t, os.WriteFile(filepath.Join(workspacePath, "image.bin"), []byte{0x89, 0x50, 0x4E, 0x47, 0x00, 0xFF, 0xFE}, 0644)) @@ -448,7 +448,7 @@ func TestProjectWorkspaceDownload_FormatJSON_BinaryRejected(t *testing.T) { func TestProjectWorkspaceDownload_FormatJSON_TooLarge(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Download JSON Big") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Download JSON Big") // Write a file larger than 1MB bigContent := make([]byte, maxEditableFileSize+1) @@ -468,7 +468,7 @@ func TestProjectWorkspaceDownload_FormatJSON_TooLarge(t *testing.T) { func TestProjectWorkspaceArchive_Success(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Archive OK") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Archive OK") // Create some test files require.NoError(t, os.WriteFile(filepath.Join(workspacePath, "hello.txt"), []byte("hello world"), 0644)) @@ -501,7 +501,7 @@ func TestProjectWorkspaceArchive_Success(t *testing.T) { func TestProjectWorkspaceArchive_EmptyWorkspace(t *testing.T) { srv, _ := testServer(t) - project, _ := createTestHubNativeProject(t, srv, "WS Archive Empty") + project, _ := createTestHubManagedProject(t, srv, "WS Archive Empty") rec := doRequest(t, srv, http.MethodGet, fmt.Sprintf("/api/v1/projects/%s/workspace/archive", project.ID), nil) require.Equal(t, http.StatusOK, rec.Code) @@ -522,7 +522,7 @@ func TestProjectWorkspaceArchive_GitProjectRejected(t *testing.T) { func TestProjectWorkspaceArchive_MethodNotAllowed(t *testing.T) { srv, _ := testServer(t) - project, _ := createTestHubNativeProject(t, srv, "WS Archive Method") + project, _ := createTestHubManagedProject(t, srv, "WS Archive Method") rec := doRequest(t, srv, http.MethodPost, fmt.Sprintf("/api/v1/projects/%s/workspace/archive", project.ID), nil) assert.Equal(t, http.StatusMethodNotAllowed, rec.Code) @@ -534,7 +534,7 @@ func TestProjectWorkspaceArchive_MethodNotAllowed(t *testing.T) { func TestProjectWorkspaceWrite_CreateNewFile(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Write New") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Write New") body := ProjectWorkspaceWriteRequest{Content: "# New File\n\nHello!"} rec := doRequest(t, srv, http.MethodPut, fmt.Sprintf("/api/v1/projects/%s/workspace/files/docs/readme.md", project.ID), body) @@ -553,7 +553,7 @@ func TestProjectWorkspaceWrite_CreateNewFile(t *testing.T) { func TestProjectWorkspaceWrite_OverwriteExisting(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Write Overwrite") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Write Overwrite") require.NoError(t, os.WriteFile(filepath.Join(workspacePath, "config.yaml"), []byte("old: true"), 0644)) @@ -568,7 +568,7 @@ func TestProjectWorkspaceWrite_OverwriteExisting(t *testing.T) { func TestProjectWorkspaceWrite_ConflictDetection(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Write Conflict") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Write Conflict") filePath := filepath.Join(workspacePath, "data.txt") require.NoError(t, os.WriteFile(filePath, []byte("original"), 0644)) @@ -590,7 +590,7 @@ func TestProjectWorkspaceWrite_ConflictDetection(t *testing.T) { func TestProjectWorkspaceWrite_PathTraversalRejected(t *testing.T) { srv, _ := testServer(t) - project, _ := createTestHubNativeProject(t, srv, "WS Write Traversal") + project, _ := createTestHubManagedProject(t, srv, "WS Write Traversal") // Go's HTTP mux normalizes paths with "../" segments before they reach // the handler (returns 307 Redirect to the cleaned path). To test the @@ -613,7 +613,7 @@ func TestProjectWorkspaceWrite_PathTraversalRejected(t *testing.T) { func TestProjectWorkspace_RequiresAuth(t *testing.T) { srv, _ := testServer(t) - project, _ := createTestHubNativeProject(t, srv, "WS Auth") + project, _ := createTestHubManagedProject(t, srv, "WS Auth") endpoints := []struct { method string @@ -638,7 +638,7 @@ func TestProjectWorkspace_RequiresAuth(t *testing.T) { func TestProjectWorkspace_MethodNotAllowed(t *testing.T) { srv, _ := testServer(t) - project, _ := createTestHubNativeProject(t, srv, "WS Method") + project, _ := createTestHubManagedProject(t, srv, "WS Method") tests := []struct { method string @@ -704,7 +704,7 @@ func TestValidateWorkspaceFilePath(t *testing.T) { func TestProjectWorkspace_UploadListDelete_Integration(t *testing.T) { srv, _ := testServer(t) - project, _ := createTestHubNativeProject(t, srv, "WS Integration") + project, _ := createTestHubManagedProject(t, srv, "WS Integration") // Get baseline count (includes .scion files from project init) rec := doRequest(t, srv, http.MethodGet, fmt.Sprintf("/api/v1/projects/%s/workspace/files", project.ID), nil) @@ -754,7 +754,7 @@ func TestProjectWorkspace_UploadListDelete_Integration(t *testing.T) { func TestProjectWorkspace_SlugFormatProjectID(t *testing.T) { srv, _ := testServer(t) - project, _ := createTestHubNativeProject(t, srv, "WS Slug Format") + project, _ := createTestHubManagedProject(t, srv, "WS Slug Format") // Use {uuid}__{slug} format for project ID compositeID := project.ID + "__" + project.Slug @@ -777,7 +777,7 @@ func addSharedDirToProject(t *testing.T, srv *Server, projectID, dirName string) func TestSharedDirFiles_ListEmpty(t *testing.T) { srv, _ := testServer(t) - project, _ := createTestHubNativeProject(t, srv, "SD List Empty") + project, _ := createTestHubManagedProject(t, srv, "SD List Empty") addSharedDirToProject(t, srv, project.ID, "build-cache") @@ -792,7 +792,7 @@ func TestSharedDirFiles_ListEmpty(t *testing.T) { func TestSharedDirFiles_UploadAndList(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "SD Upload List") + project, workspacePath := createTestHubManagedProject(t, srv, "SD Upload List") addSharedDirToProject(t, srv, project.ID, "artifacts") @@ -821,7 +821,7 @@ func TestSharedDirFiles_UploadAndList(t *testing.T) { func TestSharedDirFiles_Download(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "SD Download") + project, workspacePath := createTestHubManagedProject(t, srv, "SD Download") addSharedDirToProject(t, srv, project.ID, "data") @@ -837,7 +837,7 @@ func TestSharedDirFiles_Download(t *testing.T) { func TestSharedDirFiles_Delete(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "SD Delete") + project, workspacePath := createTestHubManagedProject(t, srv, "SD Delete") addSharedDirToProject(t, srv, project.ID, "temp") @@ -856,7 +856,7 @@ func TestSharedDirFiles_Delete(t *testing.T) { func TestSharedDirFiles_UndeclaredDirRejected(t *testing.T) { srv, _ := testServer(t) - project, _ := createTestHubNativeProject(t, srv, "SD Undeclared") + project, _ := createTestHubManagedProject(t, srv, "SD Undeclared") // Try to access files in a shared dir that hasn't been declared rec := doRequest(t, srv, http.MethodGet, fmt.Sprintf("/api/v1/projects/%s/shared-dirs/nonexistent/files", project.ID), nil) @@ -904,7 +904,7 @@ func TestSharedDirFiles_GitProjectWithEmbeddedBroker(t *testing.T) { // Initialize a hub workspace so the .scion marker exists for path resolution. // This simulates a shared-workspace project that was cloned by the hub. - workspacePath, err := hubNativeProjectPath(project.Slug) + workspacePath, err := hubManagedProjectPath(project.Slug) require.NoError(t, err) scionDir := filepath.Join(workspacePath, config.DotScion) require.NoError(t, config.InitProject(scionDir, nil, config.InitProjectOpts{SkipRuntimeCheck: true})) @@ -965,7 +965,7 @@ func TestSharedDirFiles_GitProjectMultipleProviders(t *testing.T) { })) // Initialize a hub workspace so the .scion marker exists for path resolution - workspacePath, err := hubNativeProjectPath(project.Slug) + workspacePath, err := hubManagedProjectPath(project.Slug) require.NoError(t, err) scionDir := filepath.Join(workspacePath, config.DotScion) require.NoError(t, config.InitProject(scionDir, nil, config.InitProjectOpts{SkipRuntimeCheck: true})) @@ -1023,7 +1023,7 @@ func createTestSharedWorkspaceProject(t *testing.T, srv *Server, name, remote st var project store.Project require.NoError(t, json.NewDecoder(rec.Body).Decode(&project)) - workspacePath, err := hubNativeProjectPath(project.Slug) + workspacePath, err := hubManagedProjectPath(project.Slug) require.NoError(t, err) t.Cleanup(func() { @@ -1079,8 +1079,8 @@ func TestProjectWorkspaceArchive_SharedWorkspaceAllowed(t *testing.T) { func TestProjectWorkspacePull_RequiresSharedWorkspace(t *testing.T) { srv, _ := testServer(t) - // Create a regular hub-native project (not shared-workspace) - project, _ := createTestHubNativeProject(t, srv, "Pull NonShared") + // Create a regular hub-managed project (not shared-workspace) + project, _ := createTestHubManagedProject(t, srv, "Pull NonShared") rec := doRequest(t, srv, http.MethodPost, fmt.Sprintf("/api/v1/projects/%s/workspace/pull", project.ID), nil) assert.Equal(t, http.StatusConflict, rec.Code, "pull should be rejected for non-shared-workspace projects") @@ -1273,7 +1273,7 @@ func TestWalkDirSearcher_SortByModTimeDesc(t *testing.T) { func TestProjectWorkspaceList_SearchQuery(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Search Query") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Search Query") require.NoError(t, os.WriteFile(filepath.Join(workspacePath, "main.go"), []byte("go"), 0644)) require.NoError(t, os.WriteFile(filepath.Join(workspacePath, "README.md"), []byte("md"), 0644)) @@ -1295,7 +1295,7 @@ func TestProjectWorkspaceList_SearchQuery(t *testing.T) { func TestProjectWorkspaceList_LimitParam(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS Limit Param") + project, workspacePath := createTestHubManagedProject(t, srv, "WS Limit Param") for i := range 10 { require.NoError(t, os.WriteFile(filepath.Join(workspacePath, fmt.Sprintf("f%02d.txt", i)), []byte("x"), 0644)) @@ -1315,7 +1315,7 @@ func TestProjectWorkspaceList_LimitParam(t *testing.T) { func TestProjectWorkspaceList_HasMoreFalseWhenFewFiles(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "WS HasMore False") + project, workspacePath := createTestHubManagedProject(t, srv, "WS HasMore False") require.NoError(t, os.WriteFile(filepath.Join(workspacePath, "only.txt"), []byte("x"), 0644)) @@ -1330,7 +1330,7 @@ func TestProjectWorkspaceList_HasMoreFalseWhenFewFiles(t *testing.T) { func TestSharedDirFiles_SearchQuery(t *testing.T) { srv, _ := testServer(t) - project, workspacePath := createTestHubNativeProject(t, srv, "SD Search Query") + project, workspacePath := createTestHubManagedProject(t, srv, "SD Search Query") addSharedDirToProject(t, srv, project.ID, "cache") sharedDirPath := resolveTestSharedDirPath(t, workspacePath, "cache") diff --git a/pkg/hub/server.go b/pkg/hub/server.go index 46052289a..74e82c561 100644 --- a/pkg/hub/server.go +++ b/pkg/hub/server.go @@ -258,7 +258,7 @@ type RuntimeBrokerClient interface { // brokerID is used for HMAC authentication lookup. // task is an optional task string to pass to the agent on start. // projectPath is the local filesystem path to the project on the broker. - // projectSlug is the project slug for hub-native projects (no local provider path). + // projectSlug is the project slug for hub-managed projects (no local provider path). // resolvedEnv contains environment variables resolved from Hub storage (API keys, etc.). // harnessConfig is the harness config name to use for the agent (e.g. "claude", "gemini"). // resolvedSecrets contains type-aware secrets (including file-type) for auth resolution. @@ -314,7 +314,7 @@ type RuntimeBrokerClient interface { // Returns the command output, exit code, and any error. ExecAgent(ctx context.Context, brokerID, brokerEndpoint, agentID, projectID string, command []string, timeout int) (string, int, error) - // CleanupProject asks a broker to remove its local hub-native project directory. + // CleanupProject asks a broker to remove its local hub-managed project directory. // brokerID is used for HMAC authentication lookup. // 404 responses are tolerated for idempotency. CleanupProject(ctx context.Context, brokerID, brokerEndpoint, projectSlug string) error @@ -362,7 +362,7 @@ type RemoteCreateAgentRequest struct { // Only populated when GatherEnv is true. EnvSources map[string]string `json:"envSources,omitempty"` - // ProjectSlug is the project slug for hub-native projects. + // ProjectSlug is the project slug for hub-managed projects. // When set, the broker creates the workspace at ~/.scion/projects// // instead of the default worktree-based path. ProjectSlug string `json:"projectSlug,omitempty"` diff --git a/pkg/hub/workspace_handlers.go b/pkg/hub/workspace_handlers.go index dbca8e860..2759a8380 100644 --- a/pkg/hub/workspace_handlers.go +++ b/pkg/hub/workspace_handlers.go @@ -284,9 +284,9 @@ func (s *Server) handleWorkspaceSyncFrom(w http.ResponseWriter, r *http.Request, }) } - // For hub-native projects on remote brokers, also sync workspace back + // For hub-managed projects on remote brokers, also sync workspace back // to the Hub filesystem so the local copy stays up-to-date. - s.syncHubNativeWorkspaceBack(ctx, agent, storagePath) + s.syncHubManagedWorkspaceBack(ctx, agent, storagePath) writeJSON(w, http.StatusOK, SyncFromResponse{ Manifest: uploadResp.Manifest, @@ -655,22 +655,22 @@ func (e *brokerError) Error() string { return e.msg } -// syncHubNativeWorkspaceBack downloads workspace files from GCS to the Hub's local -// filesystem for hub-native projects on remote brokers. This keeps the Hub's copy +// syncHubManagedWorkspaceBack downloads workspace files from GCS to the Hub's local +// filesystem for hub-managed projects on remote brokers. This keeps the Hub's copy // (~/.scion/projects//) in sync after workspace changes on a remote broker. // This is a best-effort operation: errors are logged but do not fail the caller. -func (s *Server) syncHubNativeWorkspaceBack(ctx context.Context, agent *store.Agent, storagePath string) { +func (s *Server) syncHubManagedWorkspaceBack(ctx context.Context, agent *store.Agent, storagePath string) { if agent.ProjectID == "" { return } project, err := s.store.GetProject(ctx, agent.ProjectID) if err != nil { - s.workspaceLog.Warn("syncHubNativeWorkspaceBack: failed to get project", "agent_id", agent.ID, "project_id", agent.ProjectID, "error", err) + s.workspaceLog.Warn("syncHubManagedWorkspaceBack: failed to get project", "agent_id", agent.ID, "project_id", agent.ProjectID, "error", err) return } - // Only applies to hub-native and shared-workspace projects + // Only applies to hub-managed and shared-workspace projects if project.GitRemote != "" && !project.IsSharedWorkspace() { return } @@ -691,19 +691,19 @@ func (s *Server) syncHubNativeWorkspaceBack(ctx context.Context, agent *store.Ag return } - workspacePath, err := hubNativeProjectPath(project.Slug) + workspacePath, err := hubManagedProjectPath(project.Slug) if err != nil { - s.workspaceLog.Warn("syncHubNativeWorkspaceBack: failed to get project path", "error", err) + s.workspaceLog.Warn("syncHubManagedWorkspaceBack: failed to get project path", "error", err) return } - // Use the project-level storage path for hub-native projects + // Use the project-level storage path for hub-managed projects projectStoragePath := storage.ProjectWorkspaceStoragePath(project.ID) if err := gcp.SyncFromGCS(ctx, stor.Bucket(), projectStoragePath+"/files", workspacePath); err != nil { - s.workspaceLog.Warn("syncHubNativeWorkspaceBack: GCS download failed", + s.workspaceLog.Warn("syncHubManagedWorkspaceBack: GCS download failed", "project_id", project.ID, "storagePath", projectStoragePath, "error", err) } else { - s.workspaceLog.Info("syncHubNativeWorkspaceBack: workspace synced to Hub filesystem", + s.workspaceLog.Info("syncHubManagedWorkspaceBack: workspace synced to Hub filesystem", "project_id", project.ID, "path", workspacePath) } } diff --git a/pkg/hub/workspace_handlers_test.go b/pkg/hub/workspace_handlers_test.go index d8de2c31b..03b60d804 100644 --- a/pkg/hub/workspace_handlers_test.go +++ b/pkg/hub/workspace_handlers_test.go @@ -880,7 +880,7 @@ func TestErrRuntimeBrokerError(t *testing.T) { } } -func TestSyncHubNativeWorkspaceBack_SkipsGitProject(t *testing.T) { +func TestSyncHubManagedWorkspaceBack_SkipsGitProject(t *testing.T) { srv, st := testWorkspaceServer(t) ctx := context.Background() @@ -901,20 +901,20 @@ func TestSyncHubNativeWorkspaceBack_SkipsGitProject(t *testing.T) { } // This should return without doing anything for git-backed projects - srv.syncHubNativeWorkspaceBack(ctx, agent, "workspaces/project-git-sync/agent-sync-1") + srv.syncHubManagedWorkspaceBack(ctx, agent, "workspaces/project-git-sync/agent-sync-1") // No panic/error = success } -func TestSyncHubNativeWorkspaceBack_SkipsColocatedBroker(t *testing.T) { +func TestSyncHubManagedWorkspaceBack_SkipsColocatedBroker(t *testing.T) { srv, st := testWorkspaceServer(t) ctx := context.Background() - // Create a hub-native project + // Create a hub-managed project project := &store.Project{ ID: "project-colo-sync", Slug: "project-colo-sync", Name: "Hub Native Colo", - // No GitRemote = hub-native + // No GitRemote = hub-managed } if err := st.CreateProject(ctx, project); err != nil { t.Fatalf("failed to create project: %v", err) @@ -949,11 +949,11 @@ func TestSyncHubNativeWorkspaceBack_SkipsColocatedBroker(t *testing.T) { } // Should skip sync because broker has local path - srv.syncHubNativeWorkspaceBack(ctx, agent, "workspaces/project-colo-sync/project-workspace") + srv.syncHubManagedWorkspaceBack(ctx, agent, "workspaces/project-colo-sync/project-workspace") // No panic/error = success } -func TestSyncHubNativeWorkspaceBack_NoProjectID(t *testing.T) { +func TestSyncHubManagedWorkspaceBack_NoProjectID(t *testing.T) { srv, _ := testWorkspaceServer(t) ctx := context.Background() @@ -963,6 +963,6 @@ func TestSyncHubNativeWorkspaceBack_NoProjectID(t *testing.T) { } // Should return immediately - srv.syncHubNativeWorkspaceBack(ctx, agent, "some-path") + srv.syncHubManagedWorkspaceBack(ctx, agent, "some-path") // No panic/error = success } diff --git a/pkg/runtimebroker/handlers.go b/pkg/runtimebroker/handlers.go index 7b7effdbf..0ac8e2c58 100644 --- a/pkg/runtimebroker/handlers.go +++ b/pkg/runtimebroker/handlers.go @@ -600,7 +600,7 @@ func (s *Server) createAgent(w http.ResponseWriter, r *http.Request) { // If WorkspaceStoragePath is set, download workspace from GCS (non-git bootstrap) if req.WorkspaceStoragePath != "" { - // For hub-native projects (ProjectSlug set), use the conventional path + // For hub-managed projects (ProjectSlug set), use the conventional path // ~/.scion.groves// instead of the worktree-based path. var workspaceDir string if req.ProjectSlug != "" { @@ -890,14 +890,14 @@ func (s *Server) deleteAgent(w http.ResponseWriter, r *http.Request, id, project } // If no project path was found (container missing or no annotation), check - // hub-native project directories for the agent's files. Without this, - // agents in hub-native projects (~/.scion.projects//) are silently + // hub-managed project directories for the agent's files. Without this, + // agents in hub-managed projects (~/.scion.projects//) are silently // skipped during file cleanup because the default filesystem scan only // checks the CWD-resolved project dir and global ~/.scion. if projectPath == "" && deleteFiles { - if resolved := findAgentInHubNativeProjects(id); resolved != "" { + if resolved := findAgentInHubManagedProjects(id); resolved != "" { projectPath = resolved - s.agentLifecycleLog.Debug("Resolved agent project path from hub-native projects", + s.agentLifecycleLog.Debug("Resolved agent project path from hub-managed projects", "agent_id", id, "path", projectPath) } } @@ -2234,7 +2234,7 @@ func (s *Server) resolveManagerForOpts(opts api.StartOptions) agent.Manager { // resolveProjectSettingsDir returns the directory containing settings.yaml for a project. // For linked projects, projectPath already points to the .scion directory. -// For hub-native projects, projectPath is the workspace parent, so settings +// For hub-managed projects, projectPath is the workspace parent, so settings // live in the .scion subdirectory. func resolveProjectSettingsDir(projectPath string) string { if config.GetSettingsPath(projectPath) != "" { @@ -2292,7 +2292,7 @@ func (s *Server) handleProjectBySlug(w http.ResponseWriter, r *http.Request) { } } -// deleteProject removes the local hub-native project directory for the given slug. +// deleteProject removes the local hub-managed project directory for the given slug. // Returns 204 on success (including when the directory doesn't exist). func (s *Server) deleteProject(w http.ResponseWriter, r *http.Request, slug string) { globalDir, err := config.GetGlobalDir() @@ -2348,11 +2348,11 @@ func (s *Server) deleteProject(w http.ResponseWriter, r *http.Request, slug stri return } - s.agentLifecycleLog.Info("Removed hub-native project directory", "slug", slug, "path", projectPath) + s.agentLifecycleLog.Info("Removed hub-managed project directory", "slug", slug, "path", projectPath) w.WriteHeader(http.StatusNoContent) } -// findAgentInHubNativeProjects scans hub-native project directories +// findAgentInHubManagedProjects scans hub-managed project directories // (~/.scion.projects//.scion/) for an agent directory matching the given // name. Returns the .scion dir path if found, or empty string. // This is used as a fallback when the container is missing and the agent's @@ -2361,7 +2361,7 @@ func (s *Server) deleteProject(w http.ResponseWriter, r *http.Request, slug stri // Probes both the in-project location (worktree-mode agents) and the external // per-agent state dir under ~/.scion.project-configs/ (shared-workspace agents, // whose state lives external to the shared checkout). -func findAgentInHubNativeProjects(agentName string) string { +func findAgentInHubManagedProjects(agentName string) string { globalDir, err := config.GetGlobalDir() if err != nil { return "" diff --git a/pkg/runtimebroker/handlers_test.go b/pkg/runtimebroker/handlers_test.go index fe2a42804..f2814ac90 100644 --- a/pkg/runtimebroker/handlers_test.go +++ b/pkg/runtimebroker/handlers_test.go @@ -1521,10 +1521,10 @@ func TestCreateAgentProjectHubEndpointSuppressedWhenDisabled(t *testing.T) { }) } -// TestCreateAgentHubNativeProjectSettingsEndpoint tests that createAgent with a -// hub-native project (ProjectSlug set, no ProjectPath) correctly resolves the project +// TestCreateAgentHubManagedProjectSettingsEndpoint tests that createAgent with a +// hub-managed project (ProjectSlug set, no ProjectPath) correctly resolves the project // path and uses project settings hub.endpoint from the .scion subdirectory. -func TestCreateAgentHubNativeProjectSettingsEndpoint(t *testing.T) { +func TestCreateAgentHubManagedProjectSettingsEndpoint(t *testing.T) { cfg := DefaultServerConfig() cfg.BrokerID = "test-broker-id" cfg.BrokerName = "test-host" @@ -1536,7 +1536,7 @@ func TestCreateAgentHubNativeProjectSettingsEndpoint(t *testing.T) { rt := &runtime.MockRuntime{NameFunc: func() string { return "docker" }} srv := New(cfg, mgr, rt) - // Set up a hub-native project directory at the expected path. + // Set up a hub-managed project directory at the expected path. globalDir, err := config.GetGlobalDir() if err != nil { t.Fatalf("failed to get global dir: %v", err) @@ -1548,7 +1548,7 @@ func TestCreateAgentHubNativeProjectSettingsEndpoint(t *testing.T) { } t.Cleanup(func() { os.RemoveAll(projectPath) }) - // Place settings.yaml in the .scion subdirectory (hub-native project layout) + // Place settings.yaml in the .scion subdirectory (hub-managed project layout) settingsContent := "hub:\n endpoint: https://hub.external.example.com\n" if err := os.WriteFile(filepath.Join(scionDir, "settings.yaml"), []byte(settingsContent), 0644); err != nil { t.Fatalf("failed to write settings.yaml: %v", err) @@ -1556,7 +1556,7 @@ func TestCreateAgentHubNativeProjectSettingsEndpoint(t *testing.T) { // Send createAgent request with projectSlug but no projectPath body := `{ - "name": "hub-native-agent", + "name": "hub-managed-agent", "groveSlug": "settings-test-grove", "hubEndpoint": "http://localhost:9810", "config": {"template": "claude"} @@ -1586,7 +1586,7 @@ func TestCreateAgentHubNativeProjectSettingsEndpoint(t *testing.T) { } // TestResolveProjectSettingsDir tests the helper function that resolves the -// settings directory for both linked and hub-native projects. +// settings directory for both linked and hub-managed projects. func TestResolveProjectSettingsDir(t *testing.T) { t.Run("linked project - settings at projectPath directly", func(t *testing.T) { // Linked project: projectPath = /path/to/project/.scion, settings.yaml is there @@ -1604,8 +1604,8 @@ func TestResolveProjectSettingsDir(t *testing.T) { } }) - t.Run("hub-native project - settings in .scion subdirectory", func(t *testing.T) { - // Hub-native project: projectPath = ~/.scion.groves/, settings in .scion/ + t.Run("hub-managed project - settings in .scion subdirectory", func(t *testing.T) { + // Hub-managed project: projectPath = ~/.scion.groves/, settings in .scion/ projectDir := t.TempDir() scionDir := filepath.Join(projectDir, ".scion") if err := os.MkdirAll(scionDir, 0755); err != nil { @@ -2271,7 +2271,7 @@ func TestCreateAgentWithoutProfile(t *testing.T) { } func TestProjectSlugWorkspacePath(t *testing.T) { - // Verify the workspace directory path for hub-native projects uses + // Verify the workspace directory path for hub-managed projects uses // ~/.scion.groves// instead of the worktree-based path. globalDir, err := config.GetGlobalDir() if err != nil { @@ -2321,16 +2321,16 @@ func TestCreateAgentRequest_ProjectSlugField(t *testing.T) { } func TestCreateAgentProjectSlugResolvesProjectPath(t *testing.T) { - // When ProjectSlug is set and ProjectPath is empty (hub-native project with no + // When ProjectSlug is set and ProjectPath is empty (hub-managed project with no // local provider path), the handler should resolve ProjectPath to the // conventional ~/.scion.groves// path so the agent is created in the // correct project instead of the broker's local project. srv, mgr := newTestServerWithProvisionCapture() body := `{ - "name": "hub-native-agent", + "name": "hub-managed-agent", "id": "agent-uuid-123", - "slug": "hub-native-agent", + "slug": "hub-managed-agent", "groveId": "grove-abc", "groveSlug": "my-hub-grove", "provisionOnly": true, @@ -2446,7 +2446,7 @@ func TestStartAgentProjectSettingsFallbackHubEndpoint(t *testing.T) { } }) - t.Run("hub-native project with settings in .scion subdirectory", func(t *testing.T) { + t.Run("hub-managed project with settings in .scion subdirectory", func(t *testing.T) { cfg := DefaultServerConfig() cfg.BrokerID = "test-broker-id" cfg.BrokerName = "test-host" @@ -2458,7 +2458,7 @@ func TestStartAgentProjectSettingsFallbackHubEndpoint(t *testing.T) { rt := &runtime.MockRuntime{NameFunc: func() string { return "docker" }} srv := New(cfg, mgr, rt) - // Hub-native project: projectPath is the workspace parent (~/.scion.groves/), + // Hub-managed project: projectPath is the workspace parent (~/.scion.groves/), // settings.yaml lives in the .scion subdirectory projectDir := t.TempDir() scionDir := filepath.Join(projectDir, ".scion") @@ -2718,12 +2718,12 @@ func TestStartAgentBrokerIDEnv(t *testing.T) { func TestStartAgentProjectSlugResolvesProjectPath(t *testing.T) { // When the startAgent handler receives projectSlug with no projectPath - // (hub-native project), it should resolve ProjectPath from the slug. + // (hub-managed project), it should resolve ProjectPath from the slug. srv, mgr := newTestServerWithProvisionCapture() // Start uses the agent name from the URL path body := `{"groveSlug": "my-hub-grove"}` - req := httptest.NewRequest(http.MethodPost, "/api/v1/agents/hub-native-agent/start", strings.NewReader(body)) + req := httptest.NewRequest(http.MethodPost, "/api/v1/agents/hub-managed-agent/start", strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() @@ -2838,7 +2838,7 @@ func TestCreateAgentProjectSlugInitializesScionDir(t *testing.T) { defer restoreGit() // When ProjectSlug is set and the broker has no .scion subdirectory for - // the hub-native project, the handler should create it so that + // the hub-managed project, the handler should create it so that // ResolveProjectPath resolves to projects//.scion (not projects/). // This prevents agents from being created at the wrong directory level. @@ -2961,11 +2961,11 @@ func TestDeleteProject_PathTraversal_Blocked(t *testing.T) { } } -func TestFindAgentInHubNativeProjects(t *testing.T) { +func TestFindAgentInHubManagedProjects(t *testing.T) { tmpHome := t.TempDir() t.Setenv("HOME", tmpHome) - // Create hub-native project structure with an agent directory + // Create hub-managed project structure with an agent directory projectSlug := "my-project" scionDir := filepath.Join(tmpHome, ".scion", "projects", projectSlug, ".scion") agentDir := filepath.Join(scionDir, "agents", "test-agent") @@ -2973,28 +2973,28 @@ func TestFindAgentInHubNativeProjects(t *testing.T) { t.Fatalf("failed to create agent dir: %v", err) } - // Should find the agent in the hub-native project - result := findAgentInHubNativeProjects("test-agent") + // Should find the agent in the hub-managed project + result := findAgentInHubManagedProjects("test-agent") if result != scionDir { t.Errorf("expected %q, got %q", scionDir, result) } // Should not find a non-existent agent - result = findAgentInHubNativeProjects("nonexistent-agent") + result = findAgentInHubManagedProjects("nonexistent-agent") if result != "" { t.Errorf("expected empty string for nonexistent agent, got %q", result) } // Should handle missing projects directory gracefully t.Setenv("HOME", t.TempDir()) - result = findAgentInHubNativeProjects("test-agent") + result = findAgentInHubManagedProjects("test-agent") if result != "" { t.Errorf("expected empty string when projects dir missing, got %q", result) } } -func TestDeleteAgent_HubNativeProject_NoContainer(t *testing.T) { - // Verify that deleting an agent in a hub-native project resolves the correct +func TestDeleteAgent_HubManagedProject_NoContainer(t *testing.T) { + // Verify that deleting an agent in a hub-managed project resolves the correct // project path even when the container doesn't exist (e.g. created-only // agent, pruned container). cfg := DefaultServerConfig() @@ -3010,7 +3010,7 @@ func TestDeleteAgent_HubNativeProject_NoContainer(t *testing.T) { tmpHome := t.TempDir() t.Setenv("HOME", tmpHome) - // Create hub-native project with an agent directory and config file + // Create hub-managed project with an agent directory and config file projectSlug := "hub-grove" scionDir := filepath.Join(tmpHome, ".scion", "projects", projectSlug, ".scion") agentName := "orphaned-agent" diff --git a/pkg/runtimebroker/server.go b/pkg/runtimebroker/server.go index 773bc2fcf..7e10453ea 100644 --- a/pkg/runtimebroker/server.go +++ b/pkg/runtimebroker/server.go @@ -864,7 +864,7 @@ func (s *Server) discoverAuxiliaryRuntimes() { // Collect project paths to scan var projectPaths []string - // Hub-native projects: ~/.scion/{projects,groves}//.scion/ + // Hub-managed projects: ~/.scion/{projects,groves}//.scion/ globalDir, err := config.GetGlobalDir() if err == nil { for _, dirName := range []string{"projects", "groves"} { diff --git a/pkg/runtimebroker/start_context.go b/pkg/runtimebroker/start_context.go index 837e03541..beba6431d 100644 --- a/pkg/runtimebroker/start_context.go +++ b/pkg/runtimebroker/start_context.go @@ -78,7 +78,7 @@ type startContextInputs struct { // buildStartContext unifies the common startup logic shared by createAgent, // startAgent, restartAgent, and finalizeEnv: -// - Hub-native project path resolution (ProjectSlug → ~/.scion.projects//) +// - Hub-managed project path resolution (ProjectSlug → ~/.scion.projects//) // - Merged env assembly (resolved env + config env + auth + hub endpoint + broker identity) // - Template hydration // - Git-clone env injection @@ -89,7 +89,7 @@ type startContextInputs struct { // The caller may further customize the returned startContext before calling // mgr.Start or mgr.Provision. func (s *Server) buildStartContext(ctx context.Context, in startContextInputs) (*startContext, error) { - // --- Hub-native project path resolution --- + // --- Hub-managed project path resolution --- if in.ProjectSlug != "" && in.ProjectPath == "" { globalDir, err := config.GetGlobalDir() if err != nil { @@ -105,12 +105,12 @@ func (s *Server) buildStartContext(ctx context.Context, in startContextInputs) ( } in.ProjectPath = projectsPath if s.config.Debug { - s.agentLifecycleLog.Debug("Resolved hub-native project path from slug", + s.agentLifecycleLog.Debug("Resolved hub-managed project path from slug", "agent_id", in.AgentID, "slug", in.ProjectSlug, "path", in.ProjectPath) } } - // Ensure hub-native projects have a .scion marker with project-id for + // Ensure hub-managed projects have a .scion marker with project-id for // external split storage. When the hub dispatches to a broker without a // LocalPath (e.g. auto-provided embedded broker for a linked project), the // broker creates the workspace at ~/.scion.projects//. Without a @@ -118,7 +118,7 @@ func (s *Server) buildStartContext(ctx context.Context, in startContextInputs) ( // Writing the hub's project ID enables split storage so agent homes go to // ~/.scion.project-configs/__/.scion/agents/ instead. // - // The .scion path may be a marker file (hub-native/workspace marker) or + // The .scion path may be a marker file (hub-managed/workspace marker) or // a directory (git project). This block handles both forms. // // This block also handles the case where the createAgent handler already @@ -136,7 +136,7 @@ func (s *Server) buildStartContext(ctx context.Context, in startContextInputs) ( _ = os.MkdirAll(filepath.Join(extPath, "agents"), 0755) } if s.config.Debug { - s.agentLifecycleLog.Debug("Hub-native project has marker with split storage", + s.agentLifecycleLog.Debug("Hub-managed project has marker with split storage", "agent_id", in.AgentID, "slug", in.ProjectSlug, "project_id", marker.ProjectID, "path", scionPath) } } @@ -145,7 +145,7 @@ func (s *Server) buildStartContext(ctx context.Context, in startContextInputs) ( if in.ProjectID != "" { if existingID, err := config.ReadProjectID(scionPath); err != nil || existingID == "" { if wErr := config.WriteProjectID(scionPath, in.ProjectID); wErr != nil { - s.agentLifecycleLog.Warn("Failed to write project-id for hub-native project", + s.agentLifecycleLog.Warn("Failed to write project-id for hub-managed project", "agent_id", in.AgentID, "project_id", in.ProjectID, "error", wErr) } else { if extAgents, err := config.GetGitProjectExternalAgentsDir(scionPath); err == nil && extAgents != "" { @@ -164,7 +164,7 @@ func (s *Server) buildStartContext(ctx context.Context, in startContextInputs) ( } else if in.ProjectID != "" { // .scion doesn't exist — create project dir and write a marker file if err := os.MkdirAll(in.ProjectPath, 0755); err != nil { - s.agentLifecycleLog.Warn("Failed to create project dir for hub-native project", + s.agentLifecycleLog.Warn("Failed to create project dir for hub-managed project", "agent_id", in.AgentID, "slug", in.ProjectSlug, "path", in.ProjectPath, "error", err) } else { marker := &config.ProjectMarker{ @@ -173,7 +173,7 @@ func (s *Server) buildStartContext(ctx context.Context, in startContextInputs) ( ProjectSlug: in.ProjectSlug, } if wErr := config.WriteProjectMarker(scionPath, marker); wErr != nil { - s.agentLifecycleLog.Warn("Failed to write .scion marker for hub-native project", + s.agentLifecycleLog.Warn("Failed to write .scion marker for hub-managed project", "agent_id", in.AgentID, "project_id", in.ProjectID, "error", wErr) } else { if extPath, err := marker.ExternalProjectPath(); err == nil && extPath != "" { @@ -181,7 +181,7 @@ func (s *Server) buildStartContext(ctx context.Context, in startContextInputs) ( _ = os.MkdirAll(filepath.Join(extPath, "agents"), 0755) } if s.config.Debug { - s.agentLifecycleLog.Debug("Initialized hub-native project with split storage", + s.agentLifecycleLog.Debug("Initialized hub-managed project with split storage", "agent_id", in.AgentID, "slug", in.ProjectSlug, "project_id", in.ProjectID, "path", scionPath) } } diff --git a/pkg/runtimebroker/start_context_test.go b/pkg/runtimebroker/start_context_test.go index b2e573307..bf8cd1c58 100644 --- a/pkg/runtimebroker/start_context_test.go +++ b/pkg/runtimebroker/start_context_test.go @@ -328,12 +328,12 @@ func TestBuildStartContext_AttachMode(t *testing.T) { } } -func TestBuildStartContext_HubNativeProjectWritesMarker(t *testing.T) { +func TestBuildStartContext_HubManagedProjectWritesMarker(t *testing.T) { cfg := DefaultServerConfig() cfg.StateDir = t.TempDir() srv := newTestServerForStartContext(t, cfg) - // Simulate a hub-native project: ProjectSlug set, ProjectPath pre-resolved + // Simulate a hub-managed project: ProjectSlug set, ProjectPath pre-resolved // (as the createAgent handler does for env-gather), and ProjectID from hub. projectsDir := t.TempDir() projectPath := filepath.Join(projectsDir, "web-demo") @@ -385,7 +385,7 @@ func TestBuildStartContext_HubNativeProjectWritesMarker(t *testing.T) { } } -func TestBuildStartContext_HubNativeProjectSlugResolution(t *testing.T) { +func TestBuildStartContext_HubManagedProjectSlugResolution(t *testing.T) { cfg := DefaultServerConfig() cfg.StateDir = t.TempDir() srv := newTestServerForStartContext(t, cfg) @@ -414,7 +414,7 @@ func TestBuildStartContext_HubNativeProjectSlugResolution(t *testing.T) { } } -func TestBuildStartContext_HubNativeProjectPreservesExistingProjectID(t *testing.T) { +func TestBuildStartContext_HubManagedProjectPreservesExistingProjectID(t *testing.T) { cfg := DefaultServerConfig() cfg.StateDir = t.TempDir() srv := newTestServerForStartContext(t, cfg) @@ -450,12 +450,12 @@ func TestBuildStartContext_HubNativeProjectPreservesExistingProjectID(t *testing } } -func TestBuildStartContext_HubNativeProjectPreservesExistingMarker(t *testing.T) { +func TestBuildStartContext_HubManagedProjectPreservesExistingMarker(t *testing.T) { cfg := DefaultServerConfig() cfg.StateDir = t.TempDir() srv := newTestServerForStartContext(t, cfg) - // Pre-create .scion as a marker file (hub-native project) + // Pre-create .scion as a marker file (hub-managed project) projectPath := filepath.Join(t.TempDir(), "existing-grove") if err := os.MkdirAll(projectPath, 0755); err != nil { t.Fatal(err) diff --git a/pkg/runtimebroker/types.go b/pkg/runtimebroker/types.go index 60bde494a..a5172ebdf 100644 --- a/pkg/runtimebroker/types.go +++ b/pkg/runtimebroker/types.go @@ -298,7 +298,7 @@ type CreateAgentRequest struct { // When set, the broker downloads the workspace from GCS instead of using ProjectPath. WorkspaceStoragePath string `json:"workspaceStoragePath,omitempty"` - // ProjectSlug is the project slug for hub-native projects. + // ProjectSlug is the project slug for hub-managed projects. // When set, the broker creates the workspace at ~/.scion.projects// // instead of the default worktree-based path. ProjectSlug string `json:"projectSlug,omitempty"` diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 29ea989db..3a20cfbc4 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -248,8 +248,8 @@ func WorkspaceStoragePath(groveID, agentID string) string { return "workspaces/" + groveID + "/" + agentID } -// ProjectWorkspaceStoragePath returns the storage path for a hub-native grove's shared workspace. -// Hub-native groves share a single workspace across agents (no per-agent worktrees), +// ProjectWorkspaceStoragePath returns the storage path for a hub-managed project's shared workspace. +// Hub-managed projects share a single workspace across agents (no per-agent worktrees), // so the path is grove-level rather than agent-level. func ProjectWorkspaceStoragePath(groveID string) string { return "workspaces/" + groveID + "/grove-workspace" diff --git a/pkg/store/models.go b/pkg/store/models.go index a79a47ca1..6bebc4c33 100644 --- a/pkg/store/models.go +++ b/pkg/store/models.go @@ -166,12 +166,12 @@ type AgentAppliedConfig struct { // Project type constants. // Type reflects how the project was established on the Hub: // - "linked": A pre-existing local project linked to the Hub -// - "hub-native": Created via the Hub (web UI or API) +// - "hub-managed": Created via the Hub (web UI or API) // // Whether a project is git-backed is orthogonal and indicated by the GitRemote field. const ( - ProjectTypeLinked = "linked" // Broker-linked project (local project linked to hub) - ProjectTypeHubNative = "hub-native" // Hub-native workspace + ProjectTypeLinked = "linked" // Broker-linked project (local project linked to hub) + ProjectTypeHubManaged = "hub-managed" // Hub-managed workspace ) // Workspace mode constants for git projects. @@ -225,7 +225,7 @@ type Project struct { // Computed fields (not stored, populated on read) AgentCount int `json:"agentCount,omitempty"` ActiveBrokerCount int `json:"activeBrokerCount,omitempty"` - ProjectType string `json:"projectType,omitempty"` // "git", "linked", or "hub-native" + ProjectType string `json:"projectType,omitempty"` // "linked" or "hub-managed" OwnerName string `json:"ownerName,omitempty"` // Enriched: resolved from OwnerID } diff --git a/pkg/store/sqlite/project_sync_state_test.go b/pkg/store/sqlite/project_sync_state_test.go index 82522151e..4ce823d82 100644 --- a/pkg/store/sqlite/project_sync_state_test.go +++ b/pkg/store/sqlite/project_sync_state_test.go @@ -85,7 +85,7 @@ func TestProjectSyncStateCRUD(t *testing.T) { require.NoError(t, err) assert.Len(t, states, 2) - // Delete hub-native state + // Delete hub-managed state err = s.DeleteProjectSyncState(ctx, projectID, "") require.NoError(t, err) diff --git a/pkg/store/sqlite/sqlite.go b/pkg/store/sqlite/sqlite.go index ac69baaa6..c53055318 100644 --- a/pkg/store/sqlite/sqlite.go +++ b/pkg/store/sqlite/sqlite.go @@ -2267,7 +2267,7 @@ func (s *SQLiteStore) GetProject(ctx context.Context, id string) (*store.Project } // populateProjectType sets the computed ProjectType field based on how the project was established. -// Type is "linked" (pre-existing local project linked to Hub) or "hub-native" (created via Hub). +// Type is "linked" (pre-existing local project linked to Hub) or "hub-managed" (created via Hub). // Whether a project is git-backed is orthogonal — indicated by the GitRemote field. func (s *SQLiteStore) populateProjectType(ctx context.Context, project *store.Project) { // Check if any provider has a local_path not under ~/.scion/projects/ (i.e. broker-linked) @@ -2279,7 +2279,7 @@ func (s *SQLiteStore) populateProjectType(ctx context.Context, project *store.Pr project.ProjectType = store.ProjectTypeLinked return } - project.ProjectType = store.ProjectTypeHubNative + project.ProjectType = store.ProjectTypeHubManaged } func (s *SQLiteStore) GetProjectBySlug(ctx context.Context, slug string) (*store.Project, error) { diff --git a/pkg/store/store.go b/pkg/store/store.go index 27cf63c9d..22e93e471 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -1090,7 +1090,7 @@ type ProjectSyncStateStore interface { UpsertProjectSyncState(ctx context.Context, state *ProjectSyncState) error // GetProjectSyncState retrieves sync state for a project and optional broker. - // Pass empty brokerID for hub-native project state. + // Pass empty brokerID for hub-managed project state. // Returns ErrNotFound if no sync state exists. GetProjectSyncState(ctx context.Context, projectID, brokerID string) (*ProjectSyncState, error) diff --git a/web/AGENTS.md b/web/AGENTS.md index 4a9e06330..58548e99c 100644 --- a/web/AGENTS.md +++ b/web/AGENTS.md @@ -136,7 +136,7 @@ All icons use the Shoelace `` component, which provides [Bootstrap Icon | Variant | Icon Name | Usage | |---------|-----------|-------| | **Git-backed project** | `diagram-3` | Project lists, project detail header | -| **Hub workspace** | `folder-fill` | Project lists, project detail header | +| **Hub-managed workspace** | `folder-fill` | Project lists, project detail header | | **Empty state** | `folder2-open` | No-projects placeholder | ### Profile & Config Icons diff --git a/web/src/components/pages/project-create.ts b/web/src/components/pages/project-create.ts index 5056ada77..0fab49fdb 100644 --- a/web/src/components/pages/project-create.ts +++ b/web/src/components/pages/project-create.ts @@ -17,7 +17,7 @@ /** * Project creation page component * - * Form for creating a new project, supporting both git-backed and hub-native modes. + * Form for creating a new project, supporting both git-backed and hub-managed modes. */ import { LitElement, html, css, nothing } from 'lit'; @@ -469,7 +469,7 @@ export class ScionPageProjectCreate extends LitElement { .value=${this.mode} @sl-change=${(e: Event) => this.onModeChange(e)} > - Hub Workspace + Hub-managed Workspace Git Repository
diff --git a/web/src/components/shared/git-remote-display.ts b/web/src/components/shared/git-remote-display.ts index 317c68be6..cecefc272 100644 --- a/web/src/components/shared/git-remote-display.ts +++ b/web/src/components/shared/git-remote-display.ts @@ -90,7 +90,7 @@ export class ScionGitRemoteDisplay extends LitElement { const project = this.project; if (!project.gitRemote) { - return html`${project.projectType === 'linked' ? 'Linked project' : 'Hub workspace'}`; + return html`${project.projectType === 'linked' ? 'Linked project' : 'Hub-managed workspace'}`; } const ghLink = ScionGitRemoteDisplay.gitHubLink(project.gitRemote); diff --git a/web/src/shared/types.ts b/web/src/shared/types.ts index 37b35d79f..80b7b3a22 100644 --- a/web/src/shared/types.ts +++ b/web/src/shared/types.ts @@ -122,7 +122,7 @@ export type ProjectStatus = 'active' | 'inactive' | 'error'; /** * Project type enumeration */ -export type ProjectType = 'linked' | 'hub-native'; +export type ProjectType = 'linked' | 'hub-managed'; export interface GitHubAppProjectStatus { state: 'ok' | 'degraded' | 'error' | 'unchecked';