Architecture stub for user-defined Medicine Wheel workspaces, their isolated internal records, and the governed relationships that let people and agents understand and work across several workspaces without flattening their relationality.
Version: 0.1.0 (stub)
Packages / seams: @medicine-wheel/app + @medicine-wheel/storage-provider
Document ID: rispec-workspace-scope-access-v1
Status: Stub — desired architecture; not implemented
Last Updated: 2026-08-22
Tracking Issue: jgwill/medicine-wheel#129
A person or agent enters Medicine Wheel and sees the workspaces they may meaningfully access. They can create or select a workspace and encounter one coherent wheel: its nodes, relations, ceremonies, narratives, accountability, and layouts move together as one context.
Workspaces remain distinct without becoming disconnected. A person or agent can see how authorized workspaces relate, understand the meaning and obligations of each relationship, and form an explicit working set when an activity spans several workspaces. Every record retains its origin, every write names its destination, and no relationship silently becomes permission.
The application presents six hardcoded workspace cards. Selection is browser component state that changes only the displayed name and color. It does not alter API requests, storage scope, or rendered Medicine Wheel data, and it resets on refresh.
Runtime persistence converges on one asynchronous StorageProvider contract with JSONL as the local default and Neon/Postgres as the hosted relational implementation. Provider operations are currently global to the configured store. They do not accept workspace context.
Nodes, edges, ceremonies, inquiry weaves, plan perspectives, diary entries, ceremony events, and captures use the provider seam. Narrative beats and cycles still use a legacy local store. Charts and MMOTs have direct file-backed routes. Graph layouts use one browser-storage key. Static directions and resources are reference data rather than workspace records.
The application has no authenticated user/session boundary. It can support configurable local workspaces, but it cannot honestly claim secure private multi-user access until identity and server-side membership enforcement exist.
Current Reality: One deployment exposes one global data context behind a switcher whose cards imply several workspaces but change no data. Adding simple tenant filters would isolate records while leaving relationships between workspaces unrepresented.
Desired State: Each workspace is a real data and access boundary held inside a wider relational registry. People and agents can enter one workspace, understand its authorized neighbors, and intentionally compose a working set without losing provenance or crossing permissions.
Natural Progression: Make workspace identity mandatory at the storage boundary; hold workspace-to-workspace relationships in a registry-level graph; expose explicit context and capability manifests to clients; migrate legacy data into a known default workspace; then add authenticated memberships before private multi-user publication.
The catalog exists outside any one workspace. It answers:
It holds Workspace, WorkspaceMembership, and WorkspaceRelation records. Entering a workspace does not make the catalog disappear; authorized neighboring workspaces remain reachable as context.
Each workspace owns its internal records. Every provider operation over owned data executes inside one mandatory WorkspaceScope.
The scope is enforced before lookup, mutation, counting, filtering, or deletion. A client-side filter is never treated as isolation. Direct record identifiers cannot bypass the scope.
A working set is an explicit temporary composition with one primary workspace and zero or more mounted workspaces.
Mounted reads preserve workspace_id on every result. Writes always name exactly one destination workspace. Changing the primary workspace cannot silently retarget an in-flight operation. A mounted workspace is read-only unless a separate capability explicitly permits a write to it.
An internal record may refer to a record in another workspace through a qualified identity such as:
mw://workspace/<workspace-id>/node/<node-id>
The reference preserves identity and provenance. It does not embed the target record and does not confer access. Dereferencing performs a fresh authorization check and may return a redacted, unavailable, or withdrawn result.
interface Workspace {
id: string;
slug: string;
name: string;
blurb?: string;
status: "active" | "archived";
direction?: "east" | "south" | "west" | "north";
color?: string;
repo?: string;
created_at: string;
updated_at: string;
}
direction, color, and repo are optional descriptors unless a later integration contract gives them behavior. A workspace is a scope container, not a seventh ontology node type.
interface WorkspaceMembership {
workspace_id: string;
subject_id: string;
role: "owner" | "admin" | "editor" | "viewer";
state: "invited" | "active" | "withdrawn";
created_at: string;
updated_at: string;
}
A subject_id is meaningful only after an identity/session contract exists. Local single-user mode may use one documented deployment subject but must not describe that convention as secure multi-user authorization.
interface WorkspaceRelation {
id: string;
from_workspace_id: string;
to_workspace_id: string;
relationship_type: string;
directionality: "directed" | "symmetric";
state: "proposed" | "active" | "withdrawn" | "archived";
visibility: "public" | "members" | "shared-members" | "restricted";
obligations: string[];
created_by: string;
accepted_by?: string[];
created_at: string;
updated_at: string;
metadata?: Record<string, unknown>;
}
Candidate relationship meanings include depends-on, derived-from, collaborates-with, publishes-to, mirrors, and shares-context-with. The governed vocabulary remains a design decision. Existing kinship vocabulary should be reused where its meaning truly applies, not mechanically imposed.
A relationship is its own governed record. Neither endpoint owns the truth alone. Bilateral relationships may begin as proposed and become active only after acceptance by authorized stewards from both workspaces. Withdrawal remains inspectable history.
interface WorkspaceScope {
workspace_id: string;
subject_id?: string;
capabilities: WorkspaceCapability[];
}
type WorkspaceCapability =
| "discover"
| "read"
| "write"
| "administer"
| "relate"
| "invite";
The server derives capabilities from authenticated identity and policy. Clients may receive capabilities for presentation but cannot assert them as authority.
interface WorkspaceWorkingSet {
primary_workspace_id: string;
mounted: Array<{
workspace_id: string;
capabilities: WorkspaceCapability[];
}>;
}
A working set is context, not a new persistence backend and not a merged workspace. It may be session-scoped or saved as a user preference later.
Access is evaluated separately for:
A subject may legitimately see a relation while lacking access to one endpoint’s internal records. Policy decides whether that endpoint is named, redacted, or omitted. The response must not leak a private workspace through names, counts, record identifiers, or relation metadata.
A relationship never inherits the union of both workspaces’ memberships. Membership in workspace A does not imply membership in related workspace B.
An agent receives a context manifest naming:
This manifest gives the agent relational awareness without relying on prompt text as an access-control mechanism.
Provider selection remains deployment-level:
MW_STORAGE_PROVIDER=jsonl | neon
Workspace selection scopes the selected provider; it does not select a provider.
The canonical seam should support one of these equivalent conceptual forms:
createProvider({ workspaceId, subject })
or:
provider.forWorkspace(scope)
The final API is open, but these invariants are not:
A candidate local layout is:
.mw/store/workspaces.jsonl
.mw/store/workspace-memberships.jsonl
.mw/store/workspace-relations.jsonl
.mw/store/workspaces/<workspace-id>/nodes.jsonl
.mw/store/workspaces/<workspace-id>/edges.jsonl
.mw/store/workspaces/<workspace-id>/ceremonies.jsonl
...
Per-workspace directories fit the existing file-provider shape and make the scope visible to local operators. File placement still does not replace authorization when the app serves several users.
Neon adds registry tables for workspaces, memberships, and workspace relations. Every workspace-owned table gains workspace_id with indexed, workspace-aware keys.
Nodes may use (workspace_id, id) identity. Edges use (workspace_id, from_id, to_id) and workspace-aware foreign keys so an internal edge cannot silently connect records from another workspace. Cross-workspace meaning belongs to workspace_relations or a qualified-reference contract, not an ordinary internal edge.
Row-level security may support defense in depth after identity is selected, but provider and API behavior remain responsible for the canonical contract.
A collection cannot remain accidentally global. Each family is deliberately classified as catalog-level, workspace-owned, user-global, or deployment-global.
Wheel of Workspaces reading where workspace relations can be understood before entering an internal wheel.Explicit route scope is preferred conceptually:
/api/workspaces/:workspaceId/nodes
/api/workspaces/:workspaceId/edges
/api/workspaces/:workspaceId/ceremonies
/api/workspace-relations
A compatibility window may map historical unscoped routes to the deterministic legacy/default workspace. Compatibility behavior is documented and cannot silently switch based on client UI state.
Desired Outcome: Hold one project’s relations and ceremonies without mixing another project’s history. Current Reality: The deployment presents hardcoded names over one global store. Natural Progression: Create a workspace, enter its scoped wheel, add records through workspace-aware operations, and persist the selection. Resolution: The project has a coherent wheel that remains distinct across refreshes and provider implementations.
Desired Outcome: Understand that workspace A publishes toward workspace B and what obligations that relation carries. Current Reality: Repository labels imply connections, but the workspace catalog has no first-class relational record. Natural Progression: Read the authorized registry graph and capability manifest; inspect the relationship; dereference B only if access permits. Resolution: The agent understands the relation without receiving unauthorized records from B.
Desired Outcome: Compare or coordinate authorized material from two workspaces while preserving where every record belongs.
Current Reality: Selecting one label does not scope data, and combining global results would erase provenance.
Natural Progression: Establish one primary workspace, mount the related workspace, compose authorized reads with workspace_id, and name the destination on each write.
Resolution: Work advances across both contexts without merging them or misdirecting mutations.
Desired Outcome: Stop treating two workspaces as actively related while preserving what was agreed and later changed.
Current Reality: A simple foreign-key deletion would erase relational history.
Natural Progression: Transition the relationship to withdrawn, retain its provenance and obligations, and remove it from active traversal unless history is requested.
Resolution: Current behavior changes without pretending the relationship never existed.
Desired Outcome: Upgrade without losing unscoped records or changing their meaning.
Current Reality: Existing JSONL files and SQL rows have no workspace_id.
Natural Progression: Create one deterministic legacy/default workspace, migrate or adopt existing records, map compatibility routes to it, and verify counts and relations before enabling new workspaces.
Resolution: Existing wheels remain intact while new scoped work becomes possible.
Defines what a workspace means to people, which records move together, which relationship meanings are legitimate, and what the first publishable experience includes.
Owns the cross-cutting contract among catalog, internal scope, relationship graph, working sets, providers, APIs, identity, compatibility, and publication. This role leads the first discovery pass because no single UI or storage change can establish the boundary alone.
Designs JSONL layout, Neon keys and constraints, provider parity, migrations, qualified identities, and continuity checks.
Implements workspace-aware operations, catalog routes, relationship lifecycle, capability resolution, and non-leaking error behavior.
Implements the workspace catalog, active context, relational view, working-set interactions, accessible transitions, provenance display, and browser-storage namespacing.
Validates subject resolution, memberships, bilateral relation acceptance, disclosure behavior, and server-side isolation before private multi-user access is advertised.
Builds cross-provider contract tests, migration fixtures, direct-ID isolation tests, multi-workspace interaction tests, and release documentation distinguishing local configuration from authenticated privacy.
subject_id for hosted use?repo, direction, and color descriptive metadata or integration-driving fields?This appendix records current evidence without turning repository layout into the conceptual contract.
components/workspaces-panel.tsxcomponents/navigation.tsxsrc/storage-provider/src/interface.tssrc/storage-provider/src/factory.tssrc/storage-provider/src/jsonl.tssrc/storage-provider/src/neon.tsscripts/001-create-medicine-wheel-tables.sqllib/store.ts, lib/jsonl-store.tslib/graph-layout-storage.tsrispecs/storage-provider.spec.mdrispecs/medicine-wheel.spec.mdrispecs/storage-provider.spec.md — canonical provider semanticsrispecs/ontology-core.spec.md — internal node/relation vocabulary and governed kinshiprispecs/consent-lifecycle.spec.md — withdrawal and ongoing consent semanticsrispecs/graph-viz.spec.md — possible visual language for a Wheel of Workspaces🌸: A workspace is a place where relations can be held. The catalog is the place where those places can know one another without becoming one another.