docs(boundaries): add import-topology guardrails

This commit is contained in:
Vincent Koc
2026-04-04 14:06:05 +09:00
parent 0777ddace8
commit 561db47566
4 changed files with 50 additions and 0 deletions

View File

@@ -22,6 +22,22 @@ import from this tree directly.
instead of direct imports from `src/channels/**`.
- When a bundled or third-party channel needs a new seam, add a typed SDK
contract or facade first.
- Treat channel entrypoints such as `channel.ts`, `shared.ts`,
`channel.setup.ts`, `gateway.ts`, and `outbound.ts` as hot import paths. Do
not statically pull async-only surfaces like send, monitor, probe,
directory-live, setup/login flows, or large `runtime-api.ts` barrels into
those files unless startup truly needs them.
- Prefer a small local seam such as `channel-api.ts`, `*.runtime.ts`, or
`*.runtime-api.ts` to keep heavy runtime code off the hot path.
- Do not mix static and dynamic imports for the same heavy module family across
a channel boundary change. If the path should stay lazy, keep it lazy end to
end.
- Remember that shared channel changes affect both built-in and extension
channels. Check routing, pairing, allowlists, command gating, onboarding, and
reply behavior across the full set.
## Verification
- If you touch hot channel entrypoints or lazy-loading seams, run `pnpm build`.
- For bundled plugin channel changes that can affect startup/import cost, run:
`OPENCLAW_LOCAL_CHECK=0 node scripts/profile-extension-memory.mjs --extension <id> --skip-combined --concurrency 1`

View File

@@ -23,3 +23,6 @@ nodes.
in sync.
- New Gateway methods, events, or payload fields should land through the typed
protocol definitions here rather than ad hoc JSON shapes elsewhere.
- Keep protocol modules data-first and acyclic. Do not route protocol exports
back through heavier gateway runtime or server-method helpers that make the
contract surface expensive or order-dependent at import time.

View File

@@ -26,6 +26,15 @@ can affect bundled plugins and third-party plugins.
- Do not expose implementation convenience from `src/channels/**`,
`src/agents/**`, `src/plugins/**`, or other internals unless you are
intentionally promoting a supported public contract.
- Keep public SDK entrypoints cheap at module load. If a helper is only needed
on async paths such as send, monitor, probe, directory-live, login, or setup,
prefer a narrow `*.runtime` subpath over re-exporting it through a broad SDK
barrel that hot channel entrypoints import on startup.
- Keep SDK facades acyclic. Do not add back-edge re-exports that route a
lightweight contract file back through heavier policy or runtime modules.
- Do not mix static and dynamic imports for the same runtime surface when
shaping SDK seams. If a surface must stay lazy, keep the eager side on a
light contract file and the deferred side on a dedicated runtime subpath.
- Prefer `api.runtime` or a focused SDK facade over telling extensions to reach
into host internals directly.
- When core or tests need bundled plugin helpers, prefer the plugin package
@@ -33,6 +42,14 @@ can affect bundled plugins and third-party plugins.
provider-named `src/plugin-sdk/<id>.ts` seam just to make core aware of a
bundled channel's private helpers.
## Verification
- If you touch SDK seams that affect lazy loading, hot channel entrypoints, or
bundled plugin import topology, run `pnpm build`.
- If the change can alter bundled channel startup cost, also run the isolated
entrypoint profiler for the affected plugin:
`OPENCLAW_LOCAL_CHECK=0 node scripts/profile-extension-memory.mjs --extension <id> --skip-combined --concurrency 1`
## Expanding The Boundary
- Additive, backwards-compatible changes are the default.

View File

@@ -23,6 +23,12 @@ assembly, and contract enforcement.
- Keep loader behavior aligned with the documented Plugin SDK and manifest
contracts. Do not create private backdoors that bundled plugins can use but
external plugins cannot.
- Preserve laziness in discovery and activation flows. Loader, registry, and
public-artifact changes must not eagerly import bundled plugin runtime barrels
when metadata, light exports, or typed contracts are sufficient.
- If a plugin exposes separate light and heavy runtime surfaces, keep discovery,
inventory, and setup-state checks on the light path until actual execution
needs the heavy module.
- If a loader or registry change affects plugin authors, update the public SDK,
docs, and contract tests instead of relying on incidental internals.
- Do not normalize "plugin-owned" into "core-owned" by scattering direct reads
@@ -34,3 +40,11 @@ assembly, and contract enforcement.
- Keep contract loading and contract tests on the dedicated bundled registry
path. Do not make contract validation depend on activating providers through
unrelated production resolution flows.
## Verification
- If you touch loader, registry, activation, or public-artifact code that can
change bundled plugin import fanout, run `pnpm build`.
- If the change can alter bundled plugin startup cost, re-profile the affected
plugin entrypoint with:
`OPENCLAW_LOCAL_CHECK=0 node scripts/profile-extension-memory.mjs --extension <id> --skip-combined --concurrency 1`