Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c8e19bbdcd | |||
| 2a04e9bec1 |
+1
-1
@@ -51,7 +51,7 @@ MOLECULE_ENV=development # Environment label (development/
|
||||
# MOLECULE_IN_DOCKER= # Set when running the platform inside Docker (accepts 1/0, true/false). Triggers A2A proxy to rewrite 127.0.0.1:<port> agent URLs to Docker bridge hostnames. Auto-detected via /.dockerenv; only set if detection fails or to force off.
|
||||
|
||||
# GitHub
|
||||
# GITHUB_REPO=owner/repo # Target repo for agent initial_prompt clone (e.g. Molecule-AI/molecule-monorepo). Read inside workspace containers.
|
||||
# GITHUB_REPO=owner/repo # Target repo for agent initial_prompt clone (e.g. Molecule-AI/molecule-core). Read inside workspace containers.
|
||||
# GITHUB_TOKEN= # Personal access token / installation token used by agents that clone private repos. Register as a global secret via POST /admin/secrets for propagation to workspace env. Token is used in-URL during clone and then scrubbed from .git/config via `git remote set-url`.
|
||||
|
||||
# Webhooks
|
||||
|
||||
@@ -49,8 +49,8 @@
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
git clone https://git.moleculesai.app/molecule-ai/molecule-monorepo.git
|
||||
cd molecule-monorepo
|
||||
git clone https://git.moleculesai.app/molecule-ai/molecule-core.git
|
||||
cd molecule-core
|
||||
./scripts/dev-start.sh
|
||||
```
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function PricingPage() {
|
||||
<p className="mt-2 text-ink-mid">
|
||||
We publish the{" "}
|
||||
<a
|
||||
href="https://git.moleculesai.app/molecule-ai/molecule-monorepo"
|
||||
href="https://git.moleculesai.app/molecule-ai/molecule-core"
|
||||
className="text-accent underline hover:text-accent"
|
||||
>
|
||||
full source on GitHub
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Molecule AI — Comprehensive Technical Documentation
|
||||
|
||||
> Definitive technical reference for the Molecule AI Agent Team platform.
|
||||
> Based on a full non-invasive scan of the [molecule-monorepo](https://git.moleculesai.app/molecule-ai/molecule-monorepo) repository.
|
||||
> Based on a full non-invasive scan of the [molecule-core](https://git.moleculesai.app/molecule-ai/molecule-core) repository.
|
||||
|
||||
---
|
||||
|
||||
@@ -1131,11 +1131,11 @@ Molecule AI's workspace abstraction is **runtime-agnostic by design**. A workspa
|
||||
|
||||
## Links
|
||||
|
||||
- **GitHub**: https://git.moleculesai.app/molecule-ai/molecule-monorepo
|
||||
- **Architecture Docs**: https://git.moleculesai.app/molecule-ai/molecule-monorepo/src/branch/main/docs/architecture
|
||||
- **API Protocol**: https://git.moleculesai.app/molecule-ai/molecule-monorepo/src/branch/main/docs/api-protocol
|
||||
- **Agent Runtime**: https://git.moleculesai.app/molecule-ai/molecule-monorepo/src/branch/main/docs/agent-runtime
|
||||
- **Product Docs**: https://git.moleculesai.app/molecule-ai/molecule-monorepo/src/branch/main/docs/product
|
||||
- **GitHub**: https://git.moleculesai.app/molecule-ai/molecule-core
|
||||
- **Architecture Docs**: https://git.moleculesai.app/molecule-ai/molecule-core/src/branch/main/docs/architecture
|
||||
- **API Protocol**: https://git.moleculesai.app/molecule-ai/molecule-core/src/branch/main/docs/api-protocol
|
||||
- **Agent Runtime**: https://git.moleculesai.app/molecule-ai/molecule-core/src/branch/main/docs/agent-runtime
|
||||
- **Product Docs**: https://git.moleculesai.app/molecule-ai/molecule-core/src/branch/main/docs/product
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ DATABASE_URL=postgres://dev:dev@postgres:5432/molecule?sslmode=prefer
|
||||
REDIS_URL=redis://redis:6379
|
||||
PORT=8080
|
||||
SECRETS_ENCRYPTION_KEY=dev-key-change-in-production
|
||||
WORKSPACE_DIR=/path/to/molecule-monorepo # Optional global fallback; prefer per-workspace workspace_dir in org.yaml or API
|
||||
WORKSPACE_DIR=/path/to/molecule-core # Optional global fallback; prefer per-workspace workspace_dir in org.yaml or API
|
||||
```
|
||||
|
||||
### Canvas (Next.js)
|
||||
|
||||
@@ -16,11 +16,9 @@ workspace container running on it) over an [EC2 Instance Connect
|
||||
Endpoint](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-connect-setup-ec2-instance-connect-endpoint.html).
|
||||
End users see a terminal; no direct public SSH ingress is required.
|
||||
|
||||
Tracking: originally `molecule-core#1528` (resolved 2026-04-22). The
|
||||
`molecule-core` repo has since been renamed to `molecule-monorepo` and no
|
||||
longer accepts new issues under the old name; future terminal work is
|
||||
tracked in `molecule-monorepo` issues (workspace-server scope) and in
|
||||
`molecule-controlplane` issues for the EIC / per-tenant SG path.
|
||||
Tracking: originally `molecule-core#1528` (resolved 2026-04-22). Future
|
||||
terminal work is tracked in `molecule-core` issues (workspace-server scope)
|
||||
and in `molecule-controlplane` issues for the EIC / per-tenant SG path.
|
||||
|
||||
## Where things are
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ When opencode connects to the Molecule MCP endpoint, the agent gains access to:
|
||||
"tool": "delegate_task",
|
||||
"arguments": {
|
||||
"target": "research-lead",
|
||||
"task": "Summarise the last 7 days of commits in Molecule-AI/molecule-monorepo"
|
||||
"task": "Summarise the last 7 days of commits in Molecule-AI/molecule-core"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Internal content policy
|
||||
|
||||
The `Molecule-AI/molecule-monorepo` repo is **public**. Anything internal
|
||||
The `Molecule-AI/molecule-core` repo is **public**. Anything internal
|
||||
(positioning, competitive briefs, sales playbooks, PMM/press drip, draft
|
||||
campaigns, raw research notes, ops runbooks, retrospectives) lives in
|
||||
**`Molecule-AI/internal`**.
|
||||
@@ -18,14 +18,14 @@ This page is the canonical decision tree.
|
||||
| Draft campaign asset (still iterating, not yet customer-visible) | `Molecule-AI/internal/marketing/campaigns/` |
|
||||
| Roadmap discussion, planning doc, retrospective | `Molecule-AI/internal/PLAN.md` or `Molecule-AI/internal/retrospectives/` |
|
||||
| Runbook, ops procedure, incident postmortem | `Molecule-AI/internal/runbooks/` |
|
||||
| **Public-ready** blog post (final draft, ready to ship to docs site) | `Molecule-AI/molecule-monorepo/docs/blog/` |
|
||||
| **Public-ready** tutorial / quickstart | `Molecule-AI/molecule-monorepo/docs/tutorials/` |
|
||||
| Public DevRel content (code samples, demos for users) | `Molecule-AI/molecule-monorepo/docs/devrel/` |
|
||||
| API reference, architecture docs for external developers | `Molecule-AI/molecule-monorepo/docs/api/` |
|
||||
| **Public-ready** blog post (final draft, ready to ship to docs site) | `Molecule-AI/molecule-core/docs/blog/` |
|
||||
| **Public-ready** tutorial / quickstart | `Molecule-AI/molecule-core/docs/tutorials/` |
|
||||
| Public DevRel content (code samples, demos for users) | `Molecule-AI/molecule-core/docs/devrel/` |
|
||||
| API reference, architecture docs for external developers | `Molecule-AI/molecule-core/docs/api/` |
|
||||
| Code, tests, infrastructure | wherever is appropriate inside this repo |
|
||||
|
||||
**Rule of thumb:** *"Would I be comfortable if a competitor / journalist / customer
|
||||
read this verbatim today?"* — yes → `monorepo/docs/`. No / not yet → `internal/`.
|
||||
read this verbatim today?"* — yes → `molecule-core/docs/`. No / not yet → `internal/`.
|
||||
|
||||
## Why
|
||||
|
||||
@@ -82,7 +82,7 @@ git push -u origin HEAD
|
||||
gh pr create --base main --fill
|
||||
```
|
||||
|
||||
Yes, this is more steps than `cd molecule-monorepo && git add research/foo.md`.
|
||||
Yes, this is more steps than `cd molecule-core && git add research/foo.md`.
|
||||
That cost is intentional: the friction is the point. Public space and
|
||||
internal space are different products with different audiences and
|
||||
different durability guarantees.
|
||||
|
||||
+4
-4
@@ -17,8 +17,8 @@ This path is aligned to the current repository and current UI. It gets you from
|
||||
## The one-command path
|
||||
|
||||
```bash
|
||||
git clone https://git.moleculesai.app/molecule-ai/molecule-monorepo.git
|
||||
cd molecule-monorepo
|
||||
git clone https://git.moleculesai.app/molecule-ai/molecule-core.git
|
||||
cd molecule-core
|
||||
./scripts/dev-start.sh
|
||||
```
|
||||
|
||||
@@ -42,8 +42,8 @@ If you'd rather run each component yourself — useful when you're iterating on
|
||||
### Step 1: Clone the repository
|
||||
|
||||
```bash
|
||||
git clone https://git.moleculesai.app/molecule-ai/molecule-monorepo.git
|
||||
cd molecule-monorepo
|
||||
git clone https://git.moleculesai.app/molecule-ai/molecule-core.git
|
||||
cd molecule-core
|
||||
```
|
||||
|
||||
### Step 2: Start the shared infrastructure
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#
|
||||
# Invocation (from template-hermes repo's CI):
|
||||
#
|
||||
# bash /path/to/molecule-monorepo/tools/check-template-parity.sh \
|
||||
# bash /path/to/molecule-core/tools/check-template-parity.sh \
|
||||
# install.sh start.sh
|
||||
#
|
||||
# Or inline via curl:
|
||||
|
||||
@@ -1059,13 +1059,13 @@ func expectExecuteDelegationBase(mock sqlmock.Sqlmock) {
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
// CanCommunicate: getWorkspaceRef(source) + getWorkspaceRef(target).
|
||||
// Both are root-level workspaces (parent_id=NULL) → root-level siblings → allowed.
|
||||
// Source is root-level, target is its child → parent→child communication allowed.
|
||||
mock.ExpectQuery("SELECT id, parent_id FROM workspaces WHERE id = ").
|
||||
WithArgs(testDeliverySourceID).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow(testDeliverySourceID, nil))
|
||||
mock.ExpectQuery("SELECT id, parent_id FROM workspaces WHERE id = ").
|
||||
WithArgs(testDeliveryTargetID).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow(testDeliveryTargetID, nil))
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow(testDeliveryTargetID, testDeliverySourceID))
|
||||
|
||||
// resolveAgentURL: test callers always set the URL in Redis (mr.Set ws:{id}:url),
|
||||
// so resolveAgentURL gets a cache hit and never falls back to DB.
|
||||
|
||||
@@ -246,15 +246,11 @@ func (h *DiscoveryHandler) Peers(c *gin.Context) {
|
||||
FROM workspaces w WHERE w.parent_id = $1 AND w.id != $2 AND w.status != 'removed'`,
|
||||
parentID.String, workspaceID)
|
||||
peers = append(peers, siblings...)
|
||||
} else {
|
||||
siblings, _ := queryPeerMaps(`
|
||||
SELECT w.id, w.name, COALESCE(w.role, ''), w.tier, w.status,
|
||||
COALESCE(w.agent_card, 'null'::jsonb), COALESCE(w.url, ''),
|
||||
w.parent_id, w.active_tasks
|
||||
FROM workspaces w WHERE w.parent_id IS NULL AND w.id != $1 AND w.status != 'removed'`,
|
||||
workspaceID)
|
||||
peers = append(peers, siblings...)
|
||||
}
|
||||
// Root workspaces (parent_id IS NULL) have no siblings: each org has
|
||||
// exactly one root, so a distinct workspace with parent_id IS NULL
|
||||
// belongs to a different tenant. Querying `parent_id IS NULL` would
|
||||
// leak every org root across tenants (security #1953).
|
||||
|
||||
// Children — exclude self defensively. A child row whose parent_id
|
||||
// equals the requesting workspaceID can never legitimately be the
|
||||
|
||||
@@ -223,10 +223,8 @@ func TestPeers_RootWorkspace_NoPeers(t *testing.T) {
|
||||
|
||||
peerCols := []string{"id", "name", "role", "tier", "status", "agent_card", "url", "parent_id", "active_tasks"}
|
||||
|
||||
// Siblings (other root-level workspaces) — none
|
||||
mock.ExpectQuery("SELECT w.id, w.name.*WHERE w.parent_id IS NULL AND w.id != \\$1").
|
||||
WithArgs("ws-root-alone").
|
||||
WillReturnRows(sqlmock.NewRows(peerCols))
|
||||
// Siblings — root workspaces have none (each org has exactly one root;
|
||||
// querying `parent_id IS NULL` would leak all org roots cross-tenant).
|
||||
|
||||
// Children — none. #383 added explicit `w.id != $2` self-filter.
|
||||
mock.ExpectQuery("SELECT w.id, w.name.*WHERE w.parent_id = \\$1 AND w.id != \\$2").
|
||||
@@ -959,15 +957,12 @@ func TestPeers_DevModeFailOpen_AllowsBearerlessRequest(t *testing.T) {
|
||||
|
||||
peersAuthFixtureHasLiveToken(mock, "ws-dev")
|
||||
|
||||
// Root workspace → children+parent queries still fire but the
|
||||
// parent_id lookup comes first.
|
||||
// Root workspace → no sibling query (security #1953), children+parent
|
||||
// queries still fire, and the parent_id lookup comes first.
|
||||
mock.ExpectQuery("SELECT parent_id FROM workspaces WHERE id =").
|
||||
WithArgs("ws-dev").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"parent_id"}).AddRow(nil))
|
||||
peerCols := []string{"id", "name", "role", "tier", "status", "agent_card", "url", "parent_id", "active_tasks"}
|
||||
mock.ExpectQuery("SELECT w.id.+WHERE w.parent_id IS NULL AND w.id").
|
||||
WithArgs("ws-dev").
|
||||
WillReturnRows(sqlmock.NewRows(peerCols))
|
||||
// #383 — children query gained explicit `w.id != $2` self-filter.
|
||||
mock.ExpectQuery("SELECT w.id.+WHERE w.parent_id = \\$1 AND w.id != \\$2 AND w.status").
|
||||
WithArgs("ws-dev", "ws-dev").
|
||||
|
||||
@@ -576,13 +576,13 @@ func TestDiscover_TargetOffline(t *testing.T) {
|
||||
setupTestRedis(t)
|
||||
handler := NewDiscoveryHandler()
|
||||
|
||||
// Both root-level, access allowed
|
||||
// Same-parent siblings, access allowed
|
||||
mock.ExpectQuery("SELECT id, parent_id FROM workspaces WHERE id =").
|
||||
WithArgs("ws-caller").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow("ws-caller", nil))
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow("ws-caller", "ws-parent"))
|
||||
mock.ExpectQuery("SELECT id, parent_id FROM workspaces WHERE id =").
|
||||
WithArgs("ws-off").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow("ws-off", nil))
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow("ws-off", "ws-parent"))
|
||||
|
||||
// Name + runtime lookup (discovery now queries both)
|
||||
mock.ExpectQuery("SELECT COALESCE").
|
||||
@@ -622,13 +622,13 @@ func TestCheckAccess_SiblingsAllowed(t *testing.T) {
|
||||
setupTestRedis(t)
|
||||
handler := NewDiscoveryHandler()
|
||||
|
||||
// Both root-level siblings → allowed
|
||||
// Siblings under a shared parent → allowed
|
||||
mock.ExpectQuery("SELECT id, parent_id FROM workspaces WHERE id =").
|
||||
WithArgs("ws-a").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow("ws-a", nil))
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow("ws-a", "ws-parent"))
|
||||
mock.ExpectQuery("SELECT id, parent_id FROM workspaces WHERE id =").
|
||||
WithArgs("ws-b").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow("ws-b", nil))
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow("ws-b", "ws-parent"))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
|
||||
@@ -376,14 +376,13 @@ func TestExtended_DiscoverWithCallerID(t *testing.T) {
|
||||
handler := NewDiscoveryHandler()
|
||||
|
||||
// CanCommunicate needs to look up both workspaces
|
||||
// Caller: root-level (no parent)
|
||||
// Caller and target are siblings under a shared parent
|
||||
mock.ExpectQuery("SELECT id, parent_id FROM workspaces WHERE id =").
|
||||
WithArgs("ws-caller").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow("ws-caller", nil))
|
||||
// Target: also root-level (no parent) — root-level siblings are allowed
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow("ws-caller", "ws-parent"))
|
||||
mock.ExpectQuery("SELECT id, parent_id FROM workspaces WHERE id =").
|
||||
WithArgs("ws-target").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow("ws-target", nil))
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow("ws-target", "ws-parent"))
|
||||
|
||||
// Discover handler looks up workspace name + runtime
|
||||
mock.ExpectQuery("SELECT COALESCE").
|
||||
@@ -458,14 +457,14 @@ func TestExtended_Peers(t *testing.T) {
|
||||
setupTestRedis(t)
|
||||
handler := NewDiscoveryHandler()
|
||||
|
||||
// Expect parent_id lookup for requesting workspace (root-level, no parent)
|
||||
// Expect parent_id lookup for requesting workspace (has a parent)
|
||||
mock.ExpectQuery("SELECT parent_id FROM workspaces WHERE id =").
|
||||
WithArgs("ws-peer").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"parent_id"}).AddRow(nil))
|
||||
WillReturnRows(sqlmock.NewRows([]string{"parent_id"}).AddRow("ws-parent"))
|
||||
|
||||
// Expect root-level siblings query (parent IS NULL, excluding self)
|
||||
// Expect siblings query (same parent, excluding self)
|
||||
mock.ExpectQuery("SELECT w.id, w.name").
|
||||
WithArgs("ws-peer").
|
||||
WithArgs("ws-parent", "ws-peer").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "role", "tier", "status", "agent_card", "url", "parent_id", "active_tasks"}).
|
||||
AddRow("ws-sibling", "Sibling Agent", "worker", 1, "online", []byte("null"), "http://localhost:9001", nil, 0))
|
||||
|
||||
@@ -512,13 +511,13 @@ func TestExtended_CheckAccess(t *testing.T) {
|
||||
handler := NewDiscoveryHandler()
|
||||
|
||||
// CanCommunicate will look up both workspaces
|
||||
// Both root-level — should be allowed
|
||||
// Siblings under a shared parent — should be allowed
|
||||
mock.ExpectQuery("SELECT id, parent_id FROM workspaces WHERE id =").
|
||||
WithArgs("ws-a").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow("ws-a", nil))
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow("ws-a", "ws-parent"))
|
||||
mock.ExpectQuery("SELECT id, parent_id FROM workspaces WHERE id =").
|
||||
WithArgs("ws-b").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow("ws-b", nil))
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "parent_id"}).AddRow("ws-b", "ws-parent"))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
|
||||
@@ -107,16 +107,10 @@ func (h *MCPHandler) toolListPeers(ctx context.Context, workspaceID string) (str
|
||||
log.Printf("MCP toolListPeers: sibling scan error: %v", scanErr)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rows, err := h.database.QueryContext(ctx,
|
||||
cols+` FROM workspaces w WHERE w.parent_id IS NULL AND w.id != $1 AND w.status != 'removed'`,
|
||||
workspaceID)
|
||||
if err == nil {
|
||||
if scanErr := scanPeers(rows); scanErr != nil {
|
||||
log.Printf("MCP toolListPeers: sibling scan error: %v", scanErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Root workspaces (parent_id IS NULL) have no siblings: each org has
|
||||
// exactly one root. Querying `parent_id IS NULL` would leak every org
|
||||
// root across tenants (security #1953).
|
||||
|
||||
// Children
|
||||
{
|
||||
|
||||
@@ -94,15 +94,13 @@ func CanCommunicate(callerID, targetID string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Siblings — same parent (including root-level where both have no parent)
|
||||
// Siblings — same parent (excluding root-level; each org has exactly one
|
||||
// root workspace, so two distinct root-level workspaces are in different
|
||||
// orgs and must not communicate cross-tenant).
|
||||
if caller.ParentID != nil && target.ParentID != nil &&
|
||||
*caller.ParentID == *target.ParentID {
|
||||
return true
|
||||
}
|
||||
// Root-level siblings — both have no parent
|
||||
if caller.ParentID == nil && target.ParentID == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Direct parent → child (fast path; avoids the ancestor walk)
|
||||
if target.ParentID != nil && caller.ID == *target.ParentID {
|
||||
|
||||
@@ -63,14 +63,16 @@ func TestCanCommunicate_Siblings(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanCommunicate_RootSiblings(t *testing.T) {
|
||||
func TestCanCommunicate_Denied_RootCrossTenant(t *testing.T) {
|
||||
mock := setupMockDB(t)
|
||||
// Both at root level (no parent)
|
||||
// Both at root level (no parent) but in different orgs.
|
||||
// parent_id = NULL means org root; each org has exactly one root,
|
||||
// so two distinct root workspaces must NOT communicate.
|
||||
expectLookup(mock, "ws-a", nil)
|
||||
expectLookup(mock, "ws-b", nil)
|
||||
|
||||
if !CanCommunicate("ws-a", "ws-b") {
|
||||
t.Error("root-level siblings should communicate")
|
||||
if CanCommunicate("ws-a", "ws-b") {
|
||||
t.Error("distinct root-level workspaces should NOT communicate cross-tenant")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user