Recursive deploy ladder: Company + Org tiers, mesh mark, two-tier rail

Completes the scale ladder (single → team → company → org). Every tier is a
topology whose nodes are the tier below; running a parent recursively runs each
child's sub-topology down to the leaf claws.

Backend:
- migration 0011: companies/company_teams, orgs/org_companies, topology_runs.tier
- cm-db repos for companies + orgs (mirror teams)
- TurnRequest.attrs (forwarded from node.attrs) for child-id binding
- SubTopologyExecutor (recursive_exec.rs): a parent "turn" runs the child's
  sub-topology; durability via parent updated_at keepalive + cancel propagation
  + depth cap; boxed future breaks the org→company recursion
- topology_worker selects executor by job.tier
- routes: /api/companies, /api/orgs (create/list/get/run) + unified
  /api/structure/{level}/{id} for the zoom canvas

Frontend:
- MeshMark: node-mesh brand glyph (replaces the claw PNG), tier variants
- TopologyGraphView: optional onNodeClick/nodeMeta + dark-token theming
- StructureCanvas + Breadcrumb: one recursive zoom view for every tier
  (drill down on node click, breadcrumb up); TeamRunPanel extracted + shared
- two-tier Discord-style rail: StructureRail (mesh mark + org/company/team
  glyphs + tools popover + deploy + user) | RosterColumn (selected group's
  children, or your claws); SecondaryNav for cross-cutting tools
- ComposeWizard (company/org) wired into DeployWizard; /companies + /orgs pages

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-18 14:25:06 -07:00
co-authored by Claude Opus 4.8
parent bba18a4687
commit 3eca4ed70c
58 changed files with 3221 additions and 229 deletions
+38 -3
View File
@@ -38,6 +38,9 @@ pub struct ClaimedTopologyRun {
pub checkpoint: Option<Value>,
/// Event-journal offset reached so far.
pub last_event_id: i64,
/// Deploy tier: `team` drives claws directly; `company`/`org` drive the
/// recursive sub-topology executor.
pub tier: String,
}
/// Lifecycle status + progress for a durable run (status endpoint).
@@ -79,20 +82,36 @@ pub async fn insert(
/// Enqueue a durable single-topology run job (`kind = 'run'`, `status = 'queued'`).
/// The background worker claims and executes it; the result lands in `comparison`.
/// Tier defaults to `team` (drives claws directly).
pub async fn enqueue_run(
pool: &PgPool,
id: Uuid,
workspace_id: WorkspaceId,
task: &str,
graph: &Value,
) -> Result<(), DbError> {
enqueue_run_tier(pool, id, workspace_id, task, graph, "team").await
}
/// Enqueue a durable run for a specific deploy tier (`team` | `company` | `org`).
/// The worker selects the matching executor — a `company`/`org` job runs the
/// recursive sub-topology executor, which drives each child tier in turn.
pub async fn enqueue_run_tier(
pool: &PgPool,
id: Uuid,
workspace_id: WorkspaceId,
task: &str,
graph: &Value,
tier: &str,
) -> Result<(), DbError> {
sqlx::query!(
"INSERT INTO topology_runs (id, workspace_id, task, kind, status, graph)
VALUES ($1, $2, $3, 'run', 'queued', $4)",
"INSERT INTO topology_runs (id, workspace_id, task, kind, status, graph, tier)
VALUES ($1, $2, $3, 'run', 'queued', $4, $5)",
id,
workspace_id.as_uuid(),
task,
graph,
tier,
)
.execute(pool)
.await?;
@@ -113,7 +132,7 @@ pub async fn claim_next_queued(pool: &PgPool) -> Result<Option<ClaimedTopologyRu
FOR UPDATE SKIP LOCKED
LIMIT 1
)
RETURNING id, workspace_id, task, graph, checkpoint, last_event_id",
RETURNING id, workspace_id, task, graph, checkpoint, last_event_id, tier",
)
.fetch_optional(pool)
.await?;
@@ -124,6 +143,7 @@ pub async fn claim_next_queued(pool: &PgPool) -> Result<Option<ClaimedTopologyRu
graph: r.graph,
checkpoint: r.checkpoint,
last_event_id: r.last_event_id,
tier: r.tier,
}))
}
@@ -148,6 +168,21 @@ pub async fn checkpoint(
Ok(())
}
/// Touch `updated_at` without changing the checkpoint — keeps a long-running
/// job visibly alive to the stale-run sweeper. Used by the recursive executor:
/// a parent (company/org) run can spend minutes inside one node executing a
/// child sub-topology, so every leaf turn touches the parent here to prevent
/// the 180s sweep from requeuing the parent mid-subtree.
pub async fn touch(pool: &PgPool, id: Uuid) -> Result<(), DbError> {
sqlx::query!(
"UPDATE topology_runs SET updated_at = now() WHERE id = $1",
id,
)
.execute(pool)
.await?;
Ok(())
}
/// Mark a job completed and store its final result blob.
pub async fn complete(pool: &PgPool, id: Uuid, result: &Value) -> Result<(), DbError> {
sqlx::query!(