Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions .harness/checklists/workspace-tenancy--plan-local-20260806a.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{
"schema_version": 1,
"plan_id": "plan-commonplace-workspace-tenancy-20260806a",
"tenant_slug": "Travis-Gilbert",
"workstream": "workspace-tenancy",
"created_at": "2026-08-06",
"sources": [
"docs/plans/workspace-tenancy/SPEC-COMMONPLACE-WORKSPACE-TENANCY-1.0.md",
"docs/plans/workspace-tenancy/VERIFY-FIRST.md",
"docs/plans/workspace-tenancy/DECISION-ISOLATION.md"
],
"items": [
{
"id": "WT0",
"title": "Durable SPEC + PLAN + Verify-first",
"status": "done",
"oracle_class": "artifact",
"evidence_class": "artifact"
},
{
"id": "WT9",
"title": "DECISION-ISOLATION.md",
"status": "done",
"oracle_class": "artifact",
"evidence_class": "artifact",
"evidence_note": "Near-term single instance multi-workspace-object; revisit triggers named."
},
{
"id": "WT4",
"title": "Remove boot clone; welcome root; refuse WORKSPACE_REPO",
"status": "verifying",
"oracle_class": "live",
"evidence_class": "artifact",
"evidence_note": "entrypoint/Dockerfile/railway updated; live proof after deploy + unset Railway WORKSPACE_REPO."
},
{
"id": "WT8",
"title": "gate:multitenant first cut",
"status": "partial",
"oracle_class": "test",
"evidence_class": "test",
"evidence_note": "scripts/check-multitenant.mjs; inventory burn-down incomplete (shell-state, mobile, harness tenant)."
},
{
"id": "WT1",
"title": "GitHub App + Connect flow",
"status": "pending",
"oracle_class": "live",
"evidence_class": "artifact",
"evidence_note": "Blocked on vendor App registration + secrets; types package scaffolded."
},
{
"id": "WT2",
"title": "Repo picker",
"status": "pending",
"oracle_class": "live"
},
{
"id": "WT3",
"title": "Token mint + credential helper",
"status": "pending",
"oracle_class": "live"
},
{
"id": "WT5",
"title": "One tree three doors",
"status": "pending",
"oracle_class": "live"
},
{
"id": "WT6",
"title": "Tenant keying through substrate",
"status": "pending",
"oracle_class": "live"
},
{
"id": "WT7",
"title": "Disconnect cascade",
"status": "pending",
"oracle_class": "live"
}
]
}
7 changes: 6 additions & 1 deletion apps/theorem-vscode/src/agent/session-opener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ export async function openIdeSession(

const agentUrl = pack.agentUrl || pack.consoleOrigin;
const token = pack.token;
const cwd = workspaceRoot && workspaceRoot.length > 0 ? workspaceRoot : '/workspace/repo';
const cwd = workspaceRoot && workspaceRoot.length > 0 ? workspaceRoot : undefined;
if (!cwd) {
throw new Error(
'theorem.chat: open a workspace folder first (no /workspace/repo default; SPEC WT4)',
);
}

const client = await HostedAcpClient.connect({
agentId: 'theorem',
Expand Down
51 changes: 51 additions & 0 deletions docs/plans/workspace-tenancy/DECISION-ISOLATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# DECISION-ISOLATION: workspace tenancy (WT9)

**Date:** 2026-08-06
**Spec:** SPEC-COMMONPLACE-WORKSPACE-TENANCY-1.0
**Status:** accepted for near-term

## Decision

**Near-term:** one Railway `commonplace-workspace` instance (and its sticky
volume) serves **many workspace objects**. Isolation is by contract:

- path `/workspace/{workspace_id}`
- per-workspace `--user-data-dir` / extensions dir (when a session is bound)
- installation token minted per git request (never in env for user code)
- tenant segment on every graph key (WT6)

Not by one container per workspace.

## Evidence

- Today: one Studio/`setsid` IDE process, one OpenWork process, one volume
(`packaging/workspace/entrypoint.sh`).
- Folder selection is launch-time `--default-folder` in packaging; `?folder=`
unproven for Studio reh-web (`VERIFY-FIRST.md` §1).
- Railway volume is already the durable mount; spinning N Studio processes per
active user is a later cost, not required for the object contract.

## What the contract must hold regardless of posture

1. No `WORKSPACE_REPO` / `WORKSPACE_REPO_URL` / boot clone.
2. Workspace id in every checkout path.
3. Token per git request via credential helper → API (WT3).
4. Folder binding per session (mechanism may evolve: process restart,
`?folder=` if proven, or per-session server).

## Revisit trigger

Revisit when any of:

- concurrent sessions on different workspaces collide on a single Studio
process (proven live), or
- volume IOPS / disk for N checkouts exceeds a measured Railway ceiling, or
- compliance requires process-level isolation between tenants.

Then swap deployment (N replicas or per-workspace workers) without redesigning
the workspace object, paths, or token lanes.

## Non-dependency

WT1–WT8 acceptance does not depend on this posture choice; only WT4’s open
mechanism references the Verify-first folder finding.
46 changes: 46 additions & 0 deletions docs/plans/workspace-tenancy/EXECUTE-REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Execute Report: workspace-tenancy (SPEC-COMMONPLACE-WORKSPACE-TENANCY-1.0)

Plan: `plan-commonplace-workspace-tenancy-20260806a`

## Summary
- Final condition: Spec boarded; Verify-first recorded; WT9 isolation decision accepted (single instance, multi workspace object); **WT4 source** removes boot clone and refuses `WORKSPACE_REPO` / `WORKSPACE_REPO_URL`; WT8 first-cut gate added; workspace path types package added. **WT1–WT3, WT5–WT7 not done.**
- Goal achieved: **partial** — product no longer *designed* to open CommonPlace via env; Connect GitHub + provision API still required before users can open arbitrary repos.
- Biggest remaining risk: Railway still has `WORKSPACE_REPO=Travis-Gilbert/CommonPlace` — **must delete before deploying WT4 entrypoint** or the container exits 78.
- Next action: unset Railway `WORKSPACE_REPO`; deploy workspace; then WT1 App registration.

## Checklist Reconciliation
| ID | Status | Evidence |
|---|---|---|
| WT0 | done | SPEC, PLAN, VERIFY-FIRST, checklist |
| WT9 | done | DECISION-ISOLATION.md |
| WT4 | verifying | entrypoint/Dockerfile/railway; live pending |
| WT8 | partial | `node scripts/check-multitenant.mjs` |
| WT1–WT3 | pending | App/picker/tokens |
| WT5–WT7 | pending | shared tree, tenant keys, cascade |

## Verify-first (leading findings)
1. Studio folder = launch `--default-folder` only; `?folder=` unproven.
2. Shared user-data dir today; WT4 needs per-workspace profiles.
3. Identity is GitHub OAuth (`githubTenantSlug`), not WorkOS (SPEC named WorkOS — record divergence).
4. Inventory: see VERIFY-FIRST.md §4.

## Changes Made
| Area | Summary |
|---|---|
| `packaging/workspace/entrypoint.sh` | No boot clone; refuse repo env; welcome dir |
| `packaging/workspace/Dockerfile` | `/workspace/welcome`; drop tenant/repo ENV defaults |
| `packaging/workspace/railway.toml` | Document retirement |
| `apps/theorem-vscode/.../session-opener.ts` | No `/workspace/repo` fallback |
| `packages/workspace-tenancy` | `workspacePath` + types |
| `scripts/check-multitenant.mjs` | WT8 first cut |

## Validation
| Check | Result |
|---|---|
| `node scripts/check-multitenant.mjs` | run in session |
| `pnpm --filter @commonplace/workspace-tenancy test` | run in session |
| Live `/IDE` empty welcome | **not run** until deploy + unset WORKSPACE_REPO |
| WT1 connect one-click | **not run** |

## Remaining Work
WT1 App + console connect; WT2 picker; WT3 credential helper; WT4 live proof + per-workspace user-data; WT5–WT7; finish WT8 inventory burn-down (shell-state, mobile, `CONSOLE_HARNESS_TENANT`).
32 changes: 32 additions & 0 deletions docs/plans/workspace-tenancy/PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Planning-Theorem: CommonPlace workspace tenancy

- **Plan id:** `plan-commonplace-workspace-tenancy-20260806a`
- **Tenant:** identity-derived (not env)
- **Created:** 2026-08-06
- **Spec:** [`SPEC-COMMONPLACE-WORKSPACE-TENANCY-1.0.md`](./SPEC-COMMONPLACE-WORKSPACE-TENANCY-1.0.md)
- **Supersedes:** service `WORKSPACE_REPO` / `WORKSPACE_REPO_URL` / boot clone into `/workspace/repo` (dissolves CR-002 sticky-singleton posture)

## Executive summary

- **Goal:** A workspace is a graph object resolved per session. Users Connect GitHub (vendor App), pick a repo, get `/workspace/{workspace_id}` shared by IDE, chat, and agents. Env never names tenant, repo, or user.
- **Law:** Environment carries how to reach a service, never which tenant, repo, or user.
- **Sequence:** Verify-first → WT9 isolation decision → WT4 remove boot clone → WT8 gate scaffold → WT1–WT3 App/picker/tokens → WT5–WT7 shared tree + cascade.

## Checklist

| ID | Task | Status |
|---|---|---|
| WT0 | Durable SPEC + PLAN + checklist + Verify-first record | in progress |
| WT9 | `DECISION-ISOLATION.md` | pending |
| WT4 | Remove boot clone; per-workspace paths; delete repo env contract | pending |
| WT8 | `gate:multitenant` + inventory burn-down | pending |
| WT1 | GitHub App + Connect flow | pending |
| WT2 | Repo picker → workspace object | pending |
| WT3 | Installation token mint + git credential helper | pending |
| WT5 | One tree three doors (live non-CommonPlace fixture) | pending |
| WT6 | Tenant keying through substrate | pending |
| WT7 | Disconnect cascade + receipt | pending |

## Validation (acceptance oracles from SPEC)

See SPEC §§ WT1–WT9 Accepted when. Leading with what is not done in EXECUTE-REPORT.
Loading
Loading