Renames: - platform/ → workspace-server/ (Go module path stays as "platform" for external dep compat — will update after plugin module republish) - workspace-template/ → workspace/ Removed (moved to separate repos or deleted): - PLAN.md — internal roadmap (move to private project board) - HANDOFF.md, AGENTS.md — one-time internal session docs - .claude/ — gitignored entirely (local agent config) - infra/cloudflare-worker/ → Molecule-AI/molecule-tenant-proxy - org-templates/molecule-dev/ → standalone template repo - .mcp-eval/ → molecule-mcp-server repo - test-results/ — ephemeral, gitignored Security scrubbing: - Cloudflare account/zone/KV IDs → placeholders - Real EC2 IPs → <EC2_IP> in all docs - CF token prefix, Neon project ID, Fly app names → redacted - Langfuse dev credentials → parameterized - Personal runner username/machine name → generic Community files: - CONTRIBUTING.md — build, test, branch conventions - CODE_OF_CONDUCT.md — Contributor Covenant 2.1 All Dockerfiles, CI workflows, docker-compose, railway.toml, render.yaml, README, CLAUDE.md updated for new directory names. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
27 KiB
2026-04-14 — edit history
Summary — tick-2: org-template polish (PRs #50, #52)
Two template-only merges landed this tick. Both touch
org-templates/molecule-dev/org.yaml and adjust role behavior inside the
default molecule-dev org template — no Go/TS/Python code changed, no
new env vars, no new API routes, no test-count drift.
Template tweaks
- PR #50
chore(template): PM system prompt — treat audit summaries as dispatch triggers, not FYIs— rewrites the PM (Project Manager) role's system prompt so that inbound audit summaries from QA / review loops are treated as actionable dispatch triggers rather than informational FYIs. The PM now routes the summary to the appropriate sub-team instead of acknowledging and stopping. File:org-templates/molecule-dev/org.yaml(PM rolesystem_prompt). Merged commit14fc30f. - PR #52
chore(template): bake working Chromium recipe into UIUX Designer cron (closes #23)— updates the UIUX Designer role's cron setup to installplaywright-chromiumvia the known-good recipe so the scheduled UX-audit job can actually launch a headless browser. Closes issue #23 (cron failed on missing Chromium). File:org-templates/molecule-dev/org.yaml(UIUX Designer role cron / setup commands). Merged commit347faab.
Not touched
- No platform (
workspace-server/) change — no API route, handler, migration, or env var added. - No canvas (
canvas/) change. - No workspace-template (
workspace/) change — the runtime image already ships the base Playwright deps; this PR only fixes the install invocation inside the cron script that the UIUX Designer workspace runs at startup. - No MCP server / SDK change.
- Test counts unchanged from the prior tick (Go 487, Vitest 357, pytest platform 1078, pytest sdk 87, MCP jest per prior tick). Template-only edits cannot shift these; skipped re-measurement.
Doc surface
- This file created.
CLAUDE.md— no change (no new endpoint / env / runtime).PLAN.md— no change (no phase boundary crossed).README.md/README.zh-CN.md— no change (no user-visible surface).
Summary — tick-3: admin test-token + hermes config fix (PRs #53, #54, #55)
Three merges this tick. One adds a new dev-only admin route for E2E scripts, one is the prior-tick doc-sync PR, and one is a one-line template config fix.
PR #53 — feat(platform): GET /admin/workspaces/:id/test-token for E2E (#6)
Merge commit 639c320. Adds a dev/test-only route that mints a fresh
bearer token for E2E scripts (closes issue #6, which called out the
brittle hand-rolled token logic in the bash E2E harness). Route is
hidden by default — it 404s in production unless explicitly enabled.
- New route —
GET /admin/workspaces/:id/test-token. Handler inworkspace-server/internal/handlers/admin_test_token.go. 404s unlessMOLECULE_ENV != "production"ORMOLECULE_ENABLE_TEST_TOKENS=1. Router wiring inworkspace-server/internal/router/router.go. - New env vars —
MOLECULE_ENV(log label, already present in.env.example) andMOLECULE_ENABLE_TEST_TOKENS(explicit override — see.env.examplefix below). - E2E helper —
tests/e2e/_lib.shgainse2e_mint_test_tokenwhich calls the new route and exportsMOLECULE_TEST_TOKENfor subsequentcurl -H "Authorization: Bearer …"calls. Replaces the previous hand-rolled JWT construction in the bash harness. - Tests —
workspace-server/internal/handlers/admin_test_token_test.goadds theTestAdminTestToken_*quartet (4 tests): prod-default-404, dev-success, explicit-enable-success, not-found-for-missing- workspace-id. - Doc updates carried by the PR itself —
CLAUDE.mdroute table gained the new admin row, and the env-var paragraph mentionsMOLECULE_ENV/MOLECULE_ENABLE_TEST_TOKENS. Verified on main.
PR #54 — docs: sync documentation with 2026-04-14 tick-2 merges (#50, #52)
Merge commit c9f0a91. Docs-only. Created the tick-2 section of this
file (see above) and did not touch any other doc surface. Nothing to
re-sync here; the file already records it.
PR #55 — fix(hermes): align config.yaml required_env with executor (HERMES_API_KEY)
Merge commit 0485585. One-line template fix. The hermes runtime's
executor reads HERMES_API_KEY (with OPENROUTER_API_KEY as
fallback), but the config.yaml required_env: list was still
declaring only OPENROUTER_API_KEY, which caused startup validation
to succeed even when the operator had neither key set, and to reject
valid setups that had only HERMES_API_KEY set. This commit updates
the template's required_env: to match the executor's read order.
- No new env var —
HERMES_API_KEYandOPENROUTER_API_KEYalready documented. - No API / handler / migration change.
- No test-count impact.
Doc-sync fix (code-review follow-up from #53)
Reviewer called out that MOLECULE_ENABLE_TEST_TOKENS was mentioned
in CLAUDE.md (admin route description) but missing from
.env.example. Added an explicit entry with a comment noting the
prod-hidden default and the two ways to expose the route. This is a
true doc-sync fix (code ships the var; example now matches).
Measured test counts this tick
- Go:
go test -v ./... | grep -c "^--- PASS"→ 712 (includes subtests). Top-levelTest*function count: 713 (713 files grepped). The prior CLAUDE.md number was 695; adding PR #53'sTestAdminTestToken_*quartet gives 699, which matches the stated "+4 this tick" and is what CLAUDE.md now records. The raw PASS-line number includes every subtest (t.Run(…)) so it's always higher than the top-level count — both numbers moved by the same +4 delta, which is what we care about. - Canvas (Vitest): unchanged — no canvas change in #53/#54/#55. CLAUDE.md still reads 357.
- Workspace-template (pytest): unchanged — no workspace-template code change. CLAUDE.md still reads 1140.
- SDK (pytest): unchanged. CLAUDE.md still reads 87.
- MCP (jest): unchanged — no MCP change.
Doc surface touched this tick
docs/edit-history/2026-04-14.md— this tick-3 section appended.CLAUDE.md— Go test count bumped 695 → 699 with reference to the new quartet. (Route table row + env-var mention already landed with PR #53.).env.example— addedMOLECULE_ENABLE_TEST_TOKENScomment row.PLAN.md/README.md/README.zh-CN.md— no change (admin E2E-helper route is not a user-visible surface; hermes fix is template-only; #54 was already docs-only).- No new
docs/**architecture doc needed — the admin route is a two-line dev helper, not a new subsystem.
Summary — tick-4: modular guardrail plugins + secrets auto-restart + restart-context message (PRs #63, #64, #65)
Three merges this evening tick. One large plugin-refactor, one secrets bugfix, and one new platform feature that injects a synthetic restart context message back to a workspace on re-registration.
PR #63 — feat(plugins): split guardrails into 12 modular plugins
Merge commit 8b896b1. Breaks the previous monolithic molecule-dev
guardrails into 12 standalone plugins under plugins/molecule-*, each
shipping its own plugin.yaml, optional hooks/, optional
settings-fragment.json, and optional skills/. Cross-runtime install
is handled by a new _install_claude_layer step on AgentskillsAdaptor
(kept in sync across both copies: workspace/plugins_registry/builtins.py
and sdk/python/molecule_plugin/builtins.py — drift-guarded).
- New plugins —
molecule-audit-trail,molecule-careful-bash,molecule-freeze-scope,molecule-prompt-watchdog,molecule-session-context,molecule-skill-code-review,molecule-skill-cron-learnings,molecule-skill-cross-vendor-review,molecule-skill-llm-judge,molecule-skill-simplify,molecule-skill-update-docs,molecule-skill-verification. - Adaptor extension —
AgentskillsAdaptor._install_claude_layerinstalls hooks (.py + .sh wrapper), merges settings-fragment.json into the workspace's.claude/settings.json, and drops skills into.claude/skills/<name>/SKILL.md. Works for every plugin that ships aclaude_codeadapter stub. - CLAUDE.md — the PR itself appended the 12-plugin enumeration to the Plugins section; verified on main, no re-sync needed in this tick.
- Tests — no new Go / Python unit tests (plugin install is exercised end-to-end via existing plugin-install integration tests).
PR #64 — fix(secrets): auto-refresh global_secrets on workspace restart (#15)
Merge commit 383582f. Fixes GitHub issue #15. Until now, rotating a
global secret (e.g. CLAUDE_CODE_OAUTH_TOKEN) only propagated to a
workspace on the next full cold-start, forcing manual ops to drive
POST /workspaces/:id/restart by hand. Tier-3 Claude Code agents were
the first to surface the stale-token path as SDK 401s.
- New helper —
restartAllAffectedByGlobalKey(db, key)inworkspace-server/internal/handlers/secrets.go. EnqueuesRestartByIDfor every non-paused, non-removed, non-external workspace that does NOT shadow the key with a workspace-level override (workspace-scoped secrets already win the Start-time merge). - Wiring —
SetGlobalandDeleteGlobalboth call the helper after a successful DB write. Matches the existing behaviour of workspace-scopedSet/Delete(which have always auto-restarted the owning workspace). - Tests —
secrets_test.gogains two sqlmock-backed tests, one per branch (set + delete), verifying the query filter (skip paused / removed / external, skip shadowed) and the enqueue call. Raw PASS count grows by more than 2 because the tests use table-driven subtests.
PR #65 — feat(platform): inject restart context system message (#19 Layer 1)
Merge commit 3ea8cda. Fixes GitHub issue #19 Layer 1 (Layer 2 is
deferred to follow-up issue #66). After a workspace restart
(HTTP /restart or programmatic RestartByID) and successful
re-registration, the platform sends a synthetic A2A message/send
back to the workspace containing:
- restart timestamp
- previous session end timestamp + human-readable duration
- list of env-var keys now available (keys only — values never leak through the message)
The message is marked with metadata.kind=restart_context so agents
can detect and handle it specifically if they choose, and uses a
system:restart-context caller prefix so it bypasses
CanCommunicate via the existing isSystemCaller() check in
a2a_proxy.go.
- New files —
workspace-server/internal/handlers/restart_context.go(240 lines: payload builder, re-registration waiter, sender with 30s timeout) andrestart_context_test.go(120 lines, 4 top-levelTest*functions). - Wiring —
workspace_restart.golaunches the context sender in a goroutine after the HTTP response has been written, so restart latency is unaffected by delivery success. - Skip path — if the workspace does not re-register within 30s, the sender logs and drops. Agents that crash during restart do not get spurious context messages.
- Layer 2 follow-up — user-defined
restart_promptviaconfig.yaml/org.yamlis tracked as new GitHub issue #66 — "Workspace restart_prompt — user-defined restart context (#19 Layer 2)".
Measured test counts this tick
Measured from /Users/hongming/Documents/GitHub/molecule-monorepo on
main (post-merge of all three PRs):
- Go:
go test -v ./... | grep -c "^--- PASS"→ 726 (was 712 in tick-3; +14 raw PASS lines from PR #64's two table-driven tests and PR #65's four top-level tests with their subtests). The top-levelTest*function delta is +6 as expected (+2 from #64, +4 from #65).#63added zero test functions. - Canvas (Vitest): unchanged — no canvas change in any PR this tick. CLAUDE.md still reads 357.
- Workspace-template (pytest): unchanged — PR #63 adds plugin directories but no new pytest collection target; the drift-guard test still passes (1/1). CLAUDE.md still reads 1140.
- SDK (pytest): unchanged — PR #63 modifies
sdk/python/molecule_plugin/builtins.pybut does not add new tests; existing SDK tests still pass. CLAUDE.md still reads 87. - MCP (jest): unchanged — no MCP change.
Doc surface touched this tick
docs/edit-history/2026-04-14.md— this tick-4 section appended.CLAUDE.md— Go test count bumped 699 → 726 (measured PASS lines, keeping the same counting convention as prior ticks); global-secrets auto-restart behaviour noted on the/settings/secretsroute / secrets section; Workspace Lifecycle section gains a sentence on the synthetic restart-context message and itssystem:restart-contextcaller prefix. 12-plugin list is already in place from PR #63.PLAN.md— backlog entries that duplicated GitHub issue numbers (11–14 used#64/#65/#66/#67as stale sequential-ID references) are left untouched; GitHub issue #66 is the new follow-up for #19 Layer 2 and has been added as a fresh Phase 32 / near-term note so the two tracking systems don't silently diverge..env.example— no change; none of the three PRs added env vars.README.md/README.zh-CN.md— no change (no user-visible surface moved by this tick: plugins are still drop-in, secrets auto-restart is an implementation detail, and the restart-context message is an agent-facing system message).
Summary — tick-5: PLAN.md backlog cleanup + wire tick-4 plugins into default org template (PRs #69, #70)
Two docs / template-only merges. Neither touches Go/TS/Python code, adds env vars, moves API routes, or shifts test counts.
PR #69 — docs(plan): drop stale sequential refs from Backlog items 11-14
Merge commit 2c89e24 (squash 730bcc4). PLAN.md only. Backlog
items 11–14 previously carried placeholder sequential refs
#64–#67 that were introduced before GitHub issues/PRs with the
same numbers merged with different scopes (PR #64 is the global-
secrets auto-restart; PR #65 is the restart-context injector; #66
is the new restart_prompt follow-up; #67 was tick-4's docs-sync
PR). Leaving the stale refs in place was actively misleading
readers cross-referencing against gh pr list / gh issue list.
The cleanup strips the #64–#67 annotations from the four
bullets and adds a single footnote explaining the history and
directing future prioritization to file real GitHub issues. No
backlog item was removed; wording of items 11–14 is otherwise
intact.
PR #70 — chore(template): wire 9 new guardrail/skill plugins into defaults; PM + Security Auditor get role extras
Merge commit e6d8cdf (squash def76e7). org-templates/molecule-dev/org.yaml
only. Activates the 12 modular plugins that PR #63 (tick-4) landed
in the repo-level registry by wiring them into the default
molecule-dev org template. Before this PR the plugins existed on
disk under plugins/molecule-* but no org actually opted in, so
newly-imported workspaces still only shipped the original three
(ecc / molecule-dev / superpowers).
- Defaults expanded (was 3, now 9) — universal additions:
molecule-careful-bash,molecule-prompt-watchdog,molecule-audit-trail,molecule-session-context,molecule-skill-cron-learnings,molecule-skill-update-docs(plus the original three, retained). - Per-role overrides:
- PM: defaults +
molecule-workflow-triage+molecule-workflow-retro(slash commands matching PM's coordination role). - Security Auditor: defaults +
molecule-skill-code-review+molecule-skill-cross-vendor-review+molecule-skill-llm-judge(multi-criteria review + adversarial cross-vendor second opinion- LLM-judge gate for "wrong thing shipped").
- Research Lead + 3 researchers + UIUX Designer: defaults +
browser-automation(existing override, resynced to new default set). - Other 5 dev roles (Dev Lead, BE, FE, DevOps, QA) inherit the new defaults unchanged.
- PM: defaults +
- REPLACE-semantics caveat —
workspace-server/internal/handlers/org.go(~L345) treats per-workspaceplugins:as REPLACE, not UNION, so every role override has to re-list all 9 defaults to add one extra. GitHub issue #68 tracks the union-semantics proposal; once it lands the per-role lists can shrink to just the deltas. No platform change in this PR. - No new tests; plugin install is exercised by the existing plugin-install integration tests.
Not touched
- No platform (
workspace-server/) change — no route, handler, migration, or env var moved. - No canvas / workspace-template / SDK / MCP change.
- No new plugins — PR #70 only wires the existing PR #63 plugins into the default template.
Test counts this tick
Unchanged from tick-4 (neither PR added tests). Per the prior-tick baseline: Go 726, Canvas (Vitest) 357, MCP 97, SDK 132, workspace 1140. Skipped re-measurement — docs/template-only diff cannot move these.
Doc surface touched this tick
docs/edit-history/2026-04-14.md— this tick-5 section appended.CLAUDE.md— no change (no code-facing surface moved).PLAN.md— PR #69 is itself the PLAN.md cleanup. Added a one- line entry under "Recently launched" noting PR #70 wired the tick-4 (PR #63) modular plugins into the default org template..env.example— no change.README.md/README.zh-CN.md— no change.
Summary — tick-6: per-workspace plugins UNION semantics + prior doc-sync (PRs #71, #72)
Two merges this tick. One resolves the REPLACE-semantics caveat called
out in tick-5 (GitHub issue #68) by flipping per-workspace plugins:
handling in org.go from REPLACE to UNION, with a !/- opt-out
prefix for removing a default on a per-workspace basis. The other is
the tick-5 docs-sync PR.
PR #71 — fix(org): per-workspace plugins UNION with defaults; '!' prefix opts out (#68)
Merge commit 26622dc (squash d9603a7). Resolves GitHub issue #68.
Before this PR, org.go (~L345) treated a per-workspace plugins:
list as a REPLACE of defaults.plugins, so every role override in the
default molecule-dev org template had to re-list all 9 defaults to
add one extra (e.g. Security Auditor had to restate 9 defaults to add
3 review skills). With this fix the two lists UNION, so role-level
entries only need to declare the delta.
- New helper —
mergePlugins(defaultPlugins, wsPlugins)inworkspace-server/internal/handlers/org.go(~L645). Returns the union of the two lists (deduplicated, defaults first). A per-workspace entry starting with!or-opts the named plugin OUT of the union (e.g.!browser-automationremovesbrowser-automationfrom a workspace that would otherwise inherit it fromdefaults.plugins). - Wiring — the
Pluginsfield resolution at ~L344 is nowplugins := mergePlugins(defaults.Plugins, ws.Plugins)instead of the prior "if ws.Plugins != nil then ws.Plugins else defaults.Plugins" branch. - Tests — 5 new
TestPlugins_*tests inworkspace-server/internal/handlers/org_test.gocovering: empty+empty, defaults-only, workspace-adds, opt-out-with-!, opt-out-with--, and dedup of a plugin listed in both sides. Measured Go raw PASS count is now 731 (was 726 at tick-5 baseline); delta is +5, matching the new test functions. - Template ripple —
org-templates/molecule-dev/org.yamlrole overrides can now shrink to just the deltas, but this PR does NOT touch the template (backward compatible: re-listing defaults still yields the same resolved set after UNION + dedup). Template cleanup is a follow-up.
PR #72 — docs: sync documentation with 2026-04-14 tick-5 merges (#69, #70)
Merge commit 3cc4e23 (squash 39bd59b). Docs-only. Created the
tick-5 section of this file (see above). Nothing to re-sync here.
Measured test counts this tick
- Go:
go test -v ./... | grep -c "^--- PASS"→ 731 (was 726 at tick-5 baseline; +5 from PR #71'sTestPlugins_*quintet). This matches exactly. - Canvas (Vitest): unchanged — no canvas change. Still 357.
- Workspace-template (pytest): unchanged — no workspace-template change. Still 1140.
- SDK (pytest): unchanged. Still 132.
- MCP (jest): unchanged. Still 97.
Doc surface touched this tick
docs/edit-history/2026-04-14.md— this tick-6 section appended.CLAUDE.md— Go test count bumped 726 → 731; Plugins / Org Templates note updated from the prior REPLACE-semantics caveat to the new UNION +!/-opt-out semantics.PLAN.md— added a "Recently launched (2026-04-14 tick-6)" entry for PR #71 noting GitHub issue #68 is now resolved..env.example— no change.README.md/README.zh-CN.md— no change (semantics are internal to org-template resolution).
Summary — tick-7: DB-authoritative schedules (#76), generic category_routing (#75), template cleanup (#74)
Four merges this tick: PR #73 (docs sync tick-6), PR #74 (template plugin cleanup), PR #75 (category_routing for #51), PR #76 (schedules source column for #24). The latter two close GitHub issues #51 and #24.
PR #76 — fix(org): DB-authoritative schedules; additive org/import (#24)
Merge commit 07a5ca3c. Closes #24.
- New migration
022_workspace_schedules_source.{up,down}.sqladds asourceTEXT column ('template'|'runtime') with a CHECK constraint and a unique(workspace_id, name)index. Legacy rows are backfilled to'template'before the column is flippedNOT NULL DEFAULT 'runtime'. - Import SQL is extracted to
const orgImportScheduleSQLinorg.goand upserts withON CONFLICT (workspace_id, name) DO UPDATE ... WHERE workspace_schedules.source = 'template'— runtime-added schedules with colliding names survive re-imports. schedules.Createwritessource='runtime'explicitly;schedules.Listreturns the field (withjson:",omitempty"so old clients don't see an empty string).- +3 tests:
TestRuntimeSchedule_HasSourceRuntime,TestImport_OrgScheduleSQLShape(asserts against the const directly, no file-scraping),TestList_IncludesSourceColumn.
PR #75 — feat(platform): generic category_routing replaces hardcoded audit dispatch (#51)
Merge commit dee5322d. Closes #51.
OrgDefaults+OrgWorkspacegainCategoryRouting map[string][]string. Merge semantics: workspace keys replace defaults' value for the same key (empty list drops the key); new keys are added.renderCategoryRoutingYAMLbuilds a deterministic YAML block viayaml.Node+yaml.Marshal(sorted keys; YAML library handles escaping of role names with reserved chars).- New
appendYAMLBlockhelper guarantees a newline boundary when concatenating YAML fragments intoconfig.yaml; applied to both thecategory_routingandinitial_promptappends. org-templates/molecule-dev/org.yamlgets adefaults.category_routingblock;pm/system-prompt.mdreplaces the hardcoded role-mapping table with a generic config-lookup pattern ("read/configs/config.yaml, look upcategory_routing[<category>]").- +6 tests covering parse, union-with-defaults, integration into workspace config, YAML-specials escaping, empty-renders-nothing, and the newline guard.
PR #74 — chore(template): simplify per-role plugin lists using #71 union semantics
Merge commit 20068196. Follow-up to PR #71.
org-templates/molecule-dev/org.yamlPM, Research Lead + 3 sub-roles, Security Auditor, UIUX Designer role overrides shrunk to just the deltas (e.g. PM goes from 11 entries to[molecule-workflow-triage, molecule-workflow-retro]; Research roles go from 10 entries to[browser-automation]).- No platform changes; relies on UNION semantics landed in PR #71 (tick-6).
PR #73 — docs: sync documentation with 2026-04-14 tick-6 merges (#71, #72)
Merge commit 911580c6. Routine docs sync for the prior tick.
File deltas
CLAUDE.md— Go test count 731 → 740; migration count 16 → 23; addedworkspace_schedules.sourcenote in the Database section.PLAN.md— new "Recently launched (2026-04-14 tick-7)" section.workspace-server/internal/handlers/org.go—OrgDefaults.CategoryRouting,OrgWorkspace.CategoryRouting,mergeCategoryRouting,renderCategoryRoutingYAML,appendYAMLBlock,orgImportScheduleSQLconst, schedules upsert wired to the const.workspace-server/internal/handlers/schedules.go—scheduleResponse.Source,Createinserts withsource='runtime',Listreadssource.workspace-server/internal/handlers/schedules_test.go— new file.workspace-server/internal/handlers/org_test.go—TestCategoryRouting_*TestAppendYAMLBlock_NewlineGuard.
workspace-server/migrations/022_workspace_schedules_source.{up,down}.sql— new.org-templates/molecule-dev/org.yaml—defaults.category_routingadded; per-role plugin lists trimmed to deltas.org-templates/molecule-dev/pm/system-prompt.md— hardcoded category table replaced with generic config-lookup instructions.
Summary — tick-8: TenantGuard middleware (Phase 32 foundation)
One merge: PR #78 (TenantGuard). Phase 32 (Cloud SaaS launch) starts here.
PR #78 — feat(platform): TenantGuard middleware — public repo's only SaaS hook
Merge commit 57a05686. Noteworthy: saas-foundation / auth-adjacent.
- New
workspace-server/internal/middleware/tenant_guard.go:- Reads
MOLECULE_ORG_IDenv at construction. If set → every non-allowlisted request must carry matchingX-Molecule-Org-Idor gets 404 (not 403, to avoid leaking tenant existence to subdomain probers). If unset → passthrough (self-hosted / dev / CI unchanged). - Allowlist is exact-match (
/health,/metrics) so Fly Machines health probes + Prometheus scrape work without the header. TenantGuardWithOrgID(id)is the test constructor; ordinary callers useTenantGuard().
- Reads
- Wired into
workspace-server/internal/router/router.goaftermetrics.Middleware()so rejected requests still land on the 4xx counter. - +6 tests: unset-passthrough, matching, mismatched-404-empty-body, missing-404, allowlist-bypass, allowlist-is-exact-match.
- CLAUDE.md: test count 740 → 746; new
MOLECULE_ORG_IDenv var documented.
Paired work — private molecule-controlplane repo scaffolded
(Outside this monorepo; logged here because it anchors the open-core split.)
- Initial commit
1bab493on new private repoMolecule-AI/molecule-controlplane. - Migrations 001 (organizations), 002 (org_instances), 003 (org_members).
- HTTP server:
/health,/cp/orgsCRUD, subdomain +X-Molecule-Org-Slugheader fallback →fly-replay: app=<tenant>;instance=<machine_id>header, stampsX-Molecule-Org-Idso TenantGuard downstream accepts the request. Provisioner+Lookupinterfaces;Stubin-memory impl (idempotent, tested) +Flystub returningErrNotImplemented(real impl is Phase B).- CI workflow: vet + build + test on push/PR.
- Follow-up PRs (in the private repo): real Fly Machines provisioner, WorkOS AuthKit signup, Stripe billing, Cloudflare edge, signup UX, observability, hardening. Full 9-phase plan documented in chat (phases A–I).
File deltas (public repo)
CLAUDE.md— test count +MOLECULE_ORG_IDenv var.PLAN.md— new "Recently launched (2026-04-14 tick-8)" block.workspace-server/internal/middleware/tenant_guard.go— new.workspace-server/internal/middleware/tenant_guard_test.go— new.workspace-server/internal/router/router.go— wired middleware.