molecule-core/docs/edit-history/2026-04-14.md
Hongming Wang d8026347e5 chore: open-source restructure — rename dirs, remove internal files, scrub secrets
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>
2026-04-18 00:24:44 -07:00

509 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 role `system_prompt`).
Merged commit `14fc30f`.
- **PR #52 `chore(template): bake working Chromium recipe into UIUX
Designer cron (closes #23)`** — updates the UIUX Designer role's cron
setup to install `playwright-chromium` via 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 commit `347faab`.
### 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 in
`workspace-server/internal/handlers/admin_test_token.go`. 404s unless
`MOLECULE_ENV != "production"` OR `MOLECULE_ENABLE_TEST_TOKENS=1`.
Router wiring in `workspace-server/internal/router/router.go`.
- **New env vars** — `MOLECULE_ENV` (log label, already present in
`.env.example`) and `MOLECULE_ENABLE_TEST_TOKENS` (explicit override
— see `.env.example` fix below).
- **E2E helper** — `tests/e2e/_lib.sh` gains `e2e_mint_test_token`
which calls the new route and exports `MOLECULE_TEST_TOKEN` for
subsequent `curl -H "Authorization: Bearer …"` calls. Replaces the
previous hand-rolled JWT construction in the bash harness.
- **Tests** — `workspace-server/internal/handlers/admin_test_token_test.go`
adds the `TestAdminTestToken_*` 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.md` route table
gained the new admin row, and the env-var paragraph mentions
`MOLECULE_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_KEY` and `OPENROUTER_API_KEY` already
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-level `Test*` function count: 713 (713 files
grepped). The prior CLAUDE.md number was 695; adding PR #53's
`TestAdminTestToken_*` 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` — added `MOLECULE_ENABLE_TEST_TOKENS` comment 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_layer`
installs 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 a
`claude_code` adapter 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)` in
`workspace-server/internal/handlers/secrets.go`. Enqueues `RestartByID` for
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** — `SetGlobal` and `DeleteGlobal` both call the helper
after a successful DB write. Matches the existing behaviour of
workspace-scoped `Set` / `Delete` (which have always auto-restarted
the owning workspace).
- **Tests** — `secrets_test.go` gains 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) and `restart_context_test.go` (120 lines, 4 top-level
`Test*` functions).
- **Wiring** — `workspace_restart.go` launches 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_prompt` via
`config.yaml` / `org.yaml` is 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-level `Test*` function delta is +6 as expected (+2 from #64,
+4 from #65). `#63` added 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.py` but 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/secrets` route /
secrets section; Workspace Lifecycle section gains a sentence on the
synthetic restart-context message and its `system:restart-context`
caller prefix. 12-plugin list is already in place from PR #63.
- `PLAN.md` — backlog entries that duplicated GitHub issue numbers
(1114 used `#64`/`#65`/`#66`/`#67` as 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 1114 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 1114 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.
- **REPLACE-semantics caveat** — `workspace-server/internal/handlers/org.go`
(~L345) treats per-workspace `plugins:` 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)` in
`workspace-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-automation` removes `browser-automation` from a
workspace that would otherwise inherit it from `defaults.plugins`).
- **Wiring** — the `Plugins` field resolution at ~L344 is now
`plugins := 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 in
`workspace-server/internal/handlers/org_test.go` covering: 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.yaml` role
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's `TestPlugins_*` 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}.sql` adds a `source`
TEXT column (`'template'` | `'runtime'`) with a CHECK constraint and a
unique `(workspace_id, name)` index. Legacy rows are backfilled to
`'template'` before the column is flipped `NOT NULL DEFAULT 'runtime'`.
- Import SQL is extracted to `const orgImportScheduleSQL` in `org.go` and
upserts with `ON CONFLICT (workspace_id, name) DO UPDATE ... WHERE
workspace_schedules.source = 'template'` — runtime-added schedules with
colliding names survive re-imports.
- `schedules.Create` writes `source='runtime'` explicitly; `schedules.List`
returns the field (with `json:",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` + `OrgWorkspace` gain `CategoryRouting map[string][]string`.
Merge semantics: workspace keys replace defaults' value for the same key
(empty list drops the key); new keys are added.
- `renderCategoryRoutingYAML` builds a deterministic YAML block via
`yaml.Node` + `yaml.Marshal` (sorted keys; YAML library handles escaping
of role names with reserved chars).
- New `appendYAMLBlock` helper guarantees a newline boundary when
concatenating YAML fragments into `config.yaml`; applied to both the
`category_routing` and `initial_prompt` appends.
- `org-templates/molecule-dev/org.yaml` gets a `defaults.category_routing`
block; `pm/system-prompt.md` replaces the hardcoded role-mapping table
with a generic config-lookup pattern ("read `/configs/config.yaml`,
look up `category_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.yaml` PM, 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; added
`workspace_schedules.source` note 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`, `orgImportScheduleSQL`
const, schedules upsert wired to the const.
- `workspace-server/internal/handlers/schedules.go``scheduleResponse.Source`,
`Create` inserts with `source='runtime'`, `List` reads `source`.
- `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_routing`
added; 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_ID` env at construction. If set → every non-allowlisted
request must carry matching `X-Molecule-Org-Id` or 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 use
`TenantGuard()`.
- Wired into `workspace-server/internal/router/router.go` after `metrics.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_ID` env var documented.
### Paired work — private `molecule-controlplane` repo scaffolded
(Outside this monorepo; logged here because it anchors the open-core split.)
- Initial commit `1bab493` on new private repo `Molecule-AI/molecule-controlplane`.
- Migrations 001 (organizations), 002 (org_instances), 003 (org_members).
- HTTP server: `/health`, `/cp/orgs` CRUD, subdomain + `X-Molecule-Org-Slug`
header fallback → `fly-replay: app=<tenant>;instance=<machine_id>` header,
stamps `X-Molecule-Org-Id` so TenantGuard downstream accepts the request.
- `Provisioner` + `Lookup` interfaces; `Stub` in-memory impl (idempotent,
tested) + `Fly` stub returning `ErrNotImplemented` (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 AI).
### File deltas (public repo)
- `CLAUDE.md` — test count + `MOLECULE_ORG_ID` env 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.