Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
9
docs/refactor/access.md
Normal file
9
docs/refactor/access.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
summary: "Redirect to /refactor/ingress-core"
|
||||
read_when:
|
||||
- Changing channel ingress, sender access, command authorization, or access-group handling
|
||||
title: "Channel access cleanup"
|
||||
sidebarTitle: "Channel access cleanup"
|
||||
---
|
||||
|
||||
The channel access cleanup plan now lives in [Ingress core deletion plan](/refactor/ingress-core).
|
||||
298
docs/refactor/acp.md
Normal file
298
docs/refactor/acp.md
Normal file
@@ -0,0 +1,298 @@
|
||||
---
|
||||
summary: "Migration plan for making ACP session and ACPX process ownership explicit"
|
||||
read_when:
|
||||
- Refactoring ACP session lifecycle or ACPX process cleanup
|
||||
- Debugging ACPX orphan processes, PID reuse, or multi-gateway cleanup safety
|
||||
- Changing sessions_list visibility for spawned ACP or subagent sessions
|
||||
- Designing ownership metadata for background tasks, ACP sessions, or process leases
|
||||
title: "ACP lifecycle refactor"
|
||||
sidebarTitle: "ACP lifecycle refactor"
|
||||
---
|
||||
|
||||
ACP lifecycle currently works, but too much of it is inferred after the fact.
|
||||
Process cleanup reconstructs ownership from PIDs, command strings, wrapper
|
||||
paths, and the live process table. Session visibility reconstructs ownership
|
||||
from session-key strings plus secondary `sessions.list({ spawnedBy })` lookups.
|
||||
That makes narrow fixes possible, but it also makes edge cases easy to miss:
|
||||
PID reuse, quoted commands, adapter grandchildren, multi-gateway state roots,
|
||||
`cancel` versus `close`, and `tree` versus `all` visibility all become separate
|
||||
places to rediscover the same ownership rules.
|
||||
|
||||
This refactor makes ownership first-class. The goal is not a new ACP product
|
||||
surface; it is a safer internal contract for the existing ACP and ACPX behavior.
|
||||
|
||||
## Goals
|
||||
|
||||
- Cleanup never signals a process unless current live evidence matches an
|
||||
OpenClaw-owned lease.
|
||||
- `cancel`, `close`, and startup reaping have distinct lifecycle intents.
|
||||
- `sessions_list`, `sessions_history`, `sessions_send`, and status checks use
|
||||
the same requester-owned session model.
|
||||
- Multi-gateway installs cannot reap each other's ACPX wrappers.
|
||||
- Old ACPX session records keep working during migration.
|
||||
- The runtime remains plugin-owned; core does not learn ACPX package details.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Replacing ACPX or changing the public `/acp` command surface.
|
||||
- Moving vendor-specific ACP adapter behavior into core.
|
||||
- Requiring users to manually clean state before upgrading.
|
||||
- Making `cancel` close reusable ACP sessions.
|
||||
|
||||
## Target Model
|
||||
|
||||
### Gateway Instance Identity
|
||||
|
||||
Each Gateway process should have a stable runtime instance id:
|
||||
|
||||
```ts
|
||||
type GatewayInstanceId = string;
|
||||
```
|
||||
|
||||
It can be generated on Gateway startup and persisted in state for the life of
|
||||
that install. It is not a security secret; it is an ownership discriminator used
|
||||
to avoid confusing one Gateway's ACP processes with another Gateway's processes.
|
||||
|
||||
### ACP Session Ownership
|
||||
|
||||
Every spawned ACP session should have normalized ownership metadata:
|
||||
|
||||
```ts
|
||||
type AcpSessionOwner = {
|
||||
sessionKey: string;
|
||||
spawnedBy?: string;
|
||||
parentSessionKey?: string;
|
||||
ownerSessionKey: string;
|
||||
agentId: string;
|
||||
backend: "acpx";
|
||||
gatewayInstanceId: GatewayInstanceId;
|
||||
createdAt: number;
|
||||
};
|
||||
```
|
||||
|
||||
The Gateway should return these fields on session rows where they are known.
|
||||
Visibility filtering should be a pure check over row metadata:
|
||||
|
||||
```ts
|
||||
canSeeSessionRow({
|
||||
row,
|
||||
requesterSessionKey,
|
||||
visibility,
|
||||
a2aPolicy,
|
||||
});
|
||||
```
|
||||
|
||||
That removes hidden secondary `sessions.list({ spawnedBy })` calls from
|
||||
visibility checks. A spawned cross-agent ACP child is requester-owned because
|
||||
the row says so, not because a second query happens to find it.
|
||||
|
||||
### ACPX Process Leases
|
||||
|
||||
Every generated wrapper launch should create a lease record:
|
||||
|
||||
```ts
|
||||
type AcpxProcessLease = {
|
||||
leaseId: string;
|
||||
gatewayInstanceId: GatewayInstanceId;
|
||||
sessionKey: string;
|
||||
wrapperRoot: string;
|
||||
wrapperPath: string;
|
||||
rootPid: number;
|
||||
processGroupId?: number;
|
||||
commandHash: string;
|
||||
startedAt: number;
|
||||
state: "open" | "closing" | "closed" | "lost";
|
||||
};
|
||||
```
|
||||
|
||||
The wrapper process should receive the lease id and gateway instance id in its
|
||||
environment:
|
||||
|
||||
```sh
|
||||
OPENCLAW_ACPX_LEASE_ID=...
|
||||
OPENCLAW_GATEWAY_INSTANCE_ID=...
|
||||
```
|
||||
|
||||
When the platform allows it, verification should prefer live process metadata
|
||||
that cannot be confused by command quoting:
|
||||
|
||||
- root PID still exists
|
||||
- live wrapper path is under `wrapperRoot`
|
||||
- process group matches the lease when available
|
||||
- environment contains the expected lease id when readable
|
||||
- command hash or executable path matches the lease
|
||||
|
||||
If the live process cannot be verified, cleanup fails closed.
|
||||
|
||||
## Lifecycle Controller
|
||||
|
||||
Introduce one ACPX lifecycle controller that owns process leases and cleanup
|
||||
policy:
|
||||
|
||||
```ts
|
||||
interface AcpxLifecycleController {
|
||||
ensureSession(input: AcpRuntimeEnsureInput): Promise<AcpRuntimeHandle>;
|
||||
cancelTurn(handle: AcpRuntimeHandle): Promise<void>;
|
||||
closeSession(input: {
|
||||
handle: AcpRuntimeHandle;
|
||||
discardPersistentState?: boolean;
|
||||
reason?: string;
|
||||
}): Promise<void>;
|
||||
reapStartupOrphans(): Promise<void>;
|
||||
verifyOwnedTree(lease: AcpxProcessLease): Promise<OwnedProcessTree | null>;
|
||||
}
|
||||
```
|
||||
|
||||
`cancelTurn` requests turn cancellation only. It must not reap reusable wrapper
|
||||
or adapter processes.
|
||||
|
||||
`closeSession` is allowed to reap, but only after loading the session record,
|
||||
loading the lease, and verifying the live process tree still belongs to that
|
||||
lease.
|
||||
|
||||
`reapStartupOrphans` starts from open leases in state. It may use the process
|
||||
table to find descendants, but it should not scan arbitrary ACP-looking
|
||||
commands first and then decide they are probably ours.
|
||||
|
||||
## Wrapper Contract
|
||||
|
||||
Generated wrappers should stay small. They should:
|
||||
|
||||
- start the adapter in a process group where supported
|
||||
- forward normal termination signals to the process group
|
||||
- detect parent death
|
||||
- on parent death, send SIGTERM, then keep the wrapper alive until the SIGKILL
|
||||
fallback runs
|
||||
- report root PID and process group id back to the lifecycle controller when
|
||||
that is available
|
||||
|
||||
Wrappers should not decide session policy. They only enforce local process-tree
|
||||
cleanup for their own adapter group.
|
||||
|
||||
## Session Visibility Contract
|
||||
|
||||
Visibility should use normalized row ownership:
|
||||
|
||||
```ts
|
||||
type SessionVisibilityInput = {
|
||||
requesterSessionKey: string;
|
||||
row: {
|
||||
key: string;
|
||||
agentId: string;
|
||||
ownerSessionKey?: string;
|
||||
spawnedBy?: string;
|
||||
parentSessionKey?: string;
|
||||
};
|
||||
visibility: "self" | "tree" | "agent" | "all";
|
||||
a2aPolicy: AgentToAgentPolicy;
|
||||
};
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- `self`: only the requester session.
|
||||
- `tree`: requester session plus rows owned by or spawned from the requester.
|
||||
- `all`: all same-agent rows, a2a-allowed cross-agent rows, and requester-owned
|
||||
spawned cross-agent rows even when general a2a is disabled.
|
||||
- `agent`: same agent only, unless an explicit owner relationship says the row
|
||||
belongs to the requester.
|
||||
|
||||
This makes `tree` and `all` monotonic: `all` must not hide an owned child that
|
||||
`tree` would show.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### Phase 1: Add Identity And Leases
|
||||
|
||||
- Add `gatewayInstanceId` to Gateway state.
|
||||
- Add an ACPX lease store under the ACPX state directory.
|
||||
- Write a lease before spawning a generated wrapper.
|
||||
- Store `leaseId` on new ACPX session records.
|
||||
- Keep existing PID and command fields for old records.
|
||||
|
||||
### Phase 2: Lease-First Cleanup
|
||||
|
||||
- Change close cleanup to load `leaseId` first.
|
||||
- Verify live process ownership against the lease before signaling.
|
||||
- Keep the current root PID and wrapper-root fallback only for legacy records.
|
||||
- Mark leases `closed` after verified cleanup.
|
||||
- Mark leases `lost` when the process is gone before cleanup.
|
||||
|
||||
### Phase 3: Lease-First Startup Reaping
|
||||
|
||||
- Startup reaping scans open leases.
|
||||
- For each lease, verify the root process and collect descendants.
|
||||
- Reap verified trees children-first.
|
||||
- Expire old `closed` and `lost` leases with a bounded retention window.
|
||||
- Keep command-marker scanning only as a temporary legacy fallback, guarded by
|
||||
wrapper root and Gateway instance where possible.
|
||||
|
||||
### Phase 4: Session Ownership Rows
|
||||
|
||||
- Add ownership metadata to Gateway session rows.
|
||||
- Teach ACPX, subagent, background-task, and session-store writers to populate
|
||||
`ownerSessionKey` or `spawnedBy`.
|
||||
- Convert session visibility checks to use row metadata.
|
||||
- Remove visibility-time secondary `sessions.list({ spawnedBy })` lookups.
|
||||
|
||||
### Phase 5: Remove Legacy Heuristics
|
||||
|
||||
After one release window:
|
||||
|
||||
- stop relying on stored root command strings for non-legacy ACPX cleanup
|
||||
- remove command-marker startup scans
|
||||
- remove visibility fallback list lookups
|
||||
- keep defensive fail-closed behavior for missing or unverifiable leases
|
||||
|
||||
## Tests
|
||||
|
||||
Add two table-driven suites.
|
||||
|
||||
Process lifecycle simulator:
|
||||
|
||||
- PID reused by unrelated process
|
||||
- PID reused by another Gateway's wrapper root
|
||||
- stored wrapper command is shell-quoted, live `ps` command is not
|
||||
- adapter child exits, grandchild remains in the process group
|
||||
- parent death SIGTERM fallback reaches SIGKILL
|
||||
- process listing unavailable
|
||||
- stale lease with missing process
|
||||
- startup orphan with wrapper, adapter child, and grandchild
|
||||
|
||||
Session visibility matrix:
|
||||
|
||||
- `self`, `tree`, `agent`, `all`
|
||||
- a2a enabled and disabled
|
||||
- same-agent row
|
||||
- cross-agent row
|
||||
- requester-owned spawned cross-agent ACP row
|
||||
- sandboxed requester clamped to `tree`
|
||||
- list, history, send, and status actions
|
||||
|
||||
The important invariant: a requester-owned spawned child is visible wherever
|
||||
the configured visibility includes the requester session tree, and `all` is not
|
||||
less capable than `tree`.
|
||||
|
||||
## Compatibility Notes
|
||||
|
||||
Old session records may not have `leaseId`. They should use the legacy
|
||||
fail-closed cleanup path:
|
||||
|
||||
- require a live root process
|
||||
- require wrapper-root ownership when a generated wrapper is expected
|
||||
- require command agreement for non-wrapper roots
|
||||
- never signal based only on stale stored PID metadata
|
||||
|
||||
If a legacy record cannot be verified, leave it alone. Startup lease cleanup and
|
||||
the next release window should eventually retire the fallback.
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- Closing an old or stale ACPX session cannot kill another Gateway's process.
|
||||
- Parent death does not leave stubborn adapter grandchildren running.
|
||||
- `cancel` aborts the active turn without closing reusable sessions.
|
||||
- `sessions_list` can show requester-owned cross-agent ACP children under both
|
||||
`tree` and `all`.
|
||||
- Startup cleanup is driven by leases, not broad command-string scans.
|
||||
- The focused process and visibility matrix tests cover every edge case that
|
||||
previously required one-off review fixes.
|
||||
131
docs/refactor/canvas.md
Normal file
131
docs/refactor/canvas.md
Normal file
@@ -0,0 +1,131 @@
|
||||
---
|
||||
summary: "Plan and audit checklist for moving Canvas out of core and into a bundled experimental plugin."
|
||||
read_when:
|
||||
- Moving Canvas host, tools, commands, docs, or protocol ownership
|
||||
- Auditing whether Canvas is still core-owned
|
||||
- Preparing or reviewing the experimental Canvas plugin PR
|
||||
title: "Canvas plugin refactor"
|
||||
---
|
||||
|
||||
# Canvas plugin refactor
|
||||
|
||||
Canvas is low-use and experimental. Treat it as a bundled plugin, not a core feature. Core may keep generic gateway, node, HTTP, auth, config, and native-client plumbing, but Canvas-specific behavior should live under `extensions/canvas`.
|
||||
|
||||
## Goal
|
||||
|
||||
Move Canvas ownership to `extensions/canvas` while preserving the current paired-node behavior:
|
||||
|
||||
- the agent-facing `canvas` tool is registered by the Canvas plugin
|
||||
- Canvas node commands are allowed only when the Canvas plugin registers them
|
||||
- A2UI host/source files live under the Canvas plugin
|
||||
- Canvas document materialization lives under the Canvas plugin
|
||||
- CLI command implementation lives under the Canvas plugin, or delegates through a plugin-owned runtime barrel
|
||||
- docs and plugin inventory describe Canvas as experimental and plugin-backed
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Do not redesign the native app Canvas UI in this refactor.
|
||||
- Do not remove Canvas protocol/client support from iOS, Android, or macOS unless a separate product decision says Canvas should be deleted.
|
||||
- Do not build a broad plugin service framework only for Canvas unless at least one other bundled plugin needs the same seam.
|
||||
|
||||
## Current branch state
|
||||
|
||||
Done:
|
||||
|
||||
- Added bundled plugin package in `extensions/canvas`.
|
||||
- Added `extensions/canvas/openclaw.plugin.json`.
|
||||
- Moved the agent `canvas` tool from `src/agents/tools/canvas-tool.ts` to `extensions/canvas/src/tool.ts`.
|
||||
- Removed core registration of `createCanvasTool` from `src/agents/openclaw-tools.ts`.
|
||||
- Moved Canvas host implementation from `src/canvas-host` to `extensions/canvas/src/host`.
|
||||
- Kept `extensions/canvas/runtime-api.ts` as the plugin-owned compatibility barrel for tests, packaging, and external public Canvas helpers.
|
||||
- Moved Canvas document materialization from `src/gateway/canvas-documents.ts` to `extensions/canvas/src/documents.ts`.
|
||||
- Moved Canvas CLI implementation and A2UI JSONL helpers into `extensions/canvas/src/cli.ts`.
|
||||
- Moved Canvas host URL and scoped capability helpers into `extensions/canvas/src`.
|
||||
- Moved Canvas node command defaults out of hardcoded core lists and into plugin `nodeInvokePolicies`.
|
||||
- Added plugin-owned Canvas host config at `plugins.entries.canvas.config.host`.
|
||||
- Moved Canvas and A2UI HTTP serving behind Canvas plugin HTTP route registration.
|
||||
- Added generic plugin WebSocket upgrade dispatch for plugin-owned HTTP routes.
|
||||
- Replaced Canvas-specific gateway host URL and node capability auth with generic hosted plugin surface and node capability helpers.
|
||||
- Added plugin-owned hosted media resolvers so Canvas document URLs resolve through the Canvas plugin instead of core importing Canvas document internals.
|
||||
- Added `api.registerNodeCliFeature(...)` so Canvas can declare `openclaw nodes canvas` as a plugin-owned node feature without manually spelling the parent command path.
|
||||
- Removed production `src/**` imports of `extensions/canvas/runtime-api.js`.
|
||||
- Moved the A2UI bundle source from `apps/shared/OpenClawKit/Tools/CanvasA2UI` to `extensions/canvas/src/host/a2ui-app`.
|
||||
- Moved A2UI build/copy implementation under `extensions/canvas/scripts` and replaced root build wiring with generic bundled-plugin asset hooks.
|
||||
- Removed the runtime legacy top-level `canvasHost` config alias.
|
||||
- Kept the Canvas doctor migration so `openclaw doctor --fix` rewrites old `canvasHost` configs into `plugins.entries.canvas.config.host`.
|
||||
- Removed old-agent Canvas protocol compatibility behind gateway protocol v4. Native clients and gateways now use only `pluginSurfaceUrls.canvas` plus `node.pluginSurface.refresh`; the deprecated `canvasHostUrl`, `canvasCapability`, and `node.canvas.capability.refresh` path is intentionally unsupported in this experimental refactor.
|
||||
- Updated generated plugin inventory to include Canvas.
|
||||
- Added plugin reference docs at `docs/plugins/reference/canvas.md`.
|
||||
|
||||
Known remaining core-owned Canvas surfaces:
|
||||
|
||||
- Native app Canvas handlers under `apps/` still intentionally consume the Canvas plugin surface
|
||||
- native app Canvas protocol/client handlers under `apps/`
|
||||
- published artifact output still uses `dist/canvas-host/a2ui` for backwards-compatible runtime lookup, but the copy step is now plugin-owned
|
||||
|
||||
## Target shape
|
||||
|
||||
`extensions/canvas` should own:
|
||||
|
||||
- plugin manifest and package metadata
|
||||
- agent tool registration
|
||||
- node invoke command policy
|
||||
- Canvas host and A2UI runtime
|
||||
- Canvas A2UI bundle source and asset build/copy scripts
|
||||
- Canvas document creation and asset resolution
|
||||
- Canvas CLI implementation
|
||||
- Canvas docs page and plugin inventory entry
|
||||
|
||||
Core should own only generic seams:
|
||||
|
||||
- plugin discovery and registration
|
||||
- generic agent tool registry
|
||||
- generic node invoke policy registry
|
||||
- generic gateway HTTP/auth and WebSocket upgrade dispatch
|
||||
- generic hosted plugin surface URL resolution
|
||||
- generic hosted media resolver registration
|
||||
- generic node capability transport
|
||||
- generic config plumbing
|
||||
- generic bundled-plugin asset hook discovery
|
||||
|
||||
Native apps may keep Canvas command handlers as clients of the protocol. They are not the plugin runtime owner.
|
||||
|
||||
## Migration steps
|
||||
|
||||
1. Treat `plugins.entries.canvas.config.host` as the plugin-owned config surface.
|
||||
2. Update docs so Canvas is described as an experimental bundled plugin.
|
||||
3. Run focused Canvas tests, plugin inventory checks, plugin SDK API checks, and build/type gates affected by runtime boundaries.
|
||||
|
||||
## Audit checklist
|
||||
|
||||
Before calling the refactor complete:
|
||||
|
||||
- `rg "src/canvas-host|../canvas-host"` returns no live source imports.
|
||||
- `rg "canvas-tool|createCanvasTool" src` finds no core-owned Canvas tool implementation.
|
||||
- `rg "canvas.present|canvas.snapshot|canvas.a2ui" src/gateway` finds no hardcoded allowlist defaults outside generic plugin policy tests.
|
||||
- `rg "extensions/canvas/runtime-api" src --glob '!**/*.test.ts'` is empty.
|
||||
- `rg "canvas-documents" src` is empty.
|
||||
- `rg "registerNodesCanvasCommands|nodes-canvas" src` is empty; the Canvas plugin registers `openclaw nodes canvas` through nested plugin CLI metadata.
|
||||
- `rg "createCanvasHostHandler|handleA2uiHttpRequest" src/gateway` returns no gateway runtime ownership.
|
||||
- `rg "apps/shared/OpenClawKit/Tools/CanvasA2UI|canvas-a2ui-copy|extensions/canvas/src/host/a2ui" scripts .github package.json` finds only compatibility wrappers or plugin-owned paths.
|
||||
- `pnpm plugins:inventory:check` passes.
|
||||
- `pnpm plugin-sdk:api:check` passes, or generated API baselines are intentionally updated and reviewed.
|
||||
- Targeted Canvas tests pass.
|
||||
- Changed-lanes tests pass for Canvas host/A2UI paths.
|
||||
- PR body explicitly says Canvas is experimental and plugin-backed.
|
||||
|
||||
## Verification commands
|
||||
|
||||
Use targeted local checks while iterating:
|
||||
|
||||
```sh
|
||||
pnpm test extensions/canvas/src/host/server.test.ts extensions/canvas/src/host/server.state-dir.test.ts extensions/canvas/src/host/file-resolver.test.ts
|
||||
pnpm test src/gateway/server.plugin-node-capability-auth.test.ts src/gateway/server-import-boundary.test.ts
|
||||
pnpm test extensions/canvas/src/config-migration.test.ts src/commands/doctor-legacy-config.migrations.test.ts
|
||||
pnpm test test/scripts/changed-lanes.test.ts test/scripts/build-all.test.ts extensions/canvas/scripts/bundle-a2ui.test.ts test/scripts/bundled-plugin-assets.test.ts extensions/canvas/scripts/copy-a2ui.test.ts src/infra/run-node.test.ts
|
||||
pnpm tsgo:extensions
|
||||
pnpm plugins:inventory:check
|
||||
pnpm plugin-sdk:api:check
|
||||
```
|
||||
|
||||
Run `pnpm build` before push if runtime barrel, lazy import, packaging, or published plugin surfaces change.
|
||||
2257
docs/refactor/database-first.md
Normal file
2257
docs/refactor/database-first.md
Normal file
File diff suppressed because it is too large
Load Diff
341
docs/refactor/ingress-core.md
Normal file
341
docs/refactor/ingress-core.md
Normal file
@@ -0,0 +1,341 @@
|
||||
---
|
||||
summary: "Deletion-first plan for moving repeated channel ingress glue into core."
|
||||
read_when:
|
||||
- Auditing why the channel ingress refactor added too much code
|
||||
- Moving route, command, event, activation, or access-group policy from bundled plugins into core
|
||||
- Reviewing whether a channel ingress helper actually deletes bundled plugin code
|
||||
title: "Ingress core deletion plan"
|
||||
sidebarTitle: "Ingress core deletion"
|
||||
---
|
||||
|
||||
# Ingress core deletion plan
|
||||
|
||||
The ingress refactor is not healthy while it adds thousands of net lines. Core
|
||||
centralization only counts when bundled plugin production code gets smaller and
|
||||
old third-party SDK compatibility is quarantined to SDK/core shims.
|
||||
|
||||
Desired runtime shape:
|
||||
|
||||
```text
|
||||
bundled plugin event
|
||||
-> extract platform facts locally
|
||||
-> resolve shared ingress once when facts are available
|
||||
-> branch on generic ingress projections/outcomes
|
||||
-> perform platform side effects locally
|
||||
|
||||
old third-party helper
|
||||
-> SDK compatibility shim
|
||||
-> shared ingress-compatible projection where possible
|
||||
-> old return shape preserved
|
||||
```
|
||||
|
||||
Bundled plugins should not translate ingress back into local `AccessResult`,
|
||||
`GroupAccessDecision`, `CommandAuthDecision`, `DmCommandAccess`, or
|
||||
`{ allowed, reasonCode }` shapes unless that type is public plugin API.
|
||||
|
||||
## Budget
|
||||
|
||||
Measured against the PR merge-base with `origin/main`, including untracked
|
||||
files.
|
||||
|
||||
```text
|
||||
merge-base 1671e7532adb
|
||||
|
||||
current:
|
||||
core production +3,922 / -546 = +3,376
|
||||
docs +601 / -17 = +584
|
||||
other +145 / -2 = +143
|
||||
plugin production +4,148 / -5,388 = -1,240
|
||||
tests +2,326 / -2,414 = -88
|
||||
total +11,142 / -8,367 = +2,775
|
||||
|
||||
required:
|
||||
plugin production <= -1,500
|
||||
core production <= +1,500, or paid for by larger plugin deletion
|
||||
tests <= +1,000
|
||||
total <= +2,000
|
||||
|
||||
stretch:
|
||||
plugin production <= -2,500
|
||||
core production <= +1,200
|
||||
total <= 0
|
||||
```
|
||||
|
||||
Minimum remaining cleanup:
|
||||
|
||||
```text
|
||||
plugin production needs 260 more net deleted lines
|
||||
total needs 775 more net deleted lines
|
||||
core production still +1,876 over standalone budget, unless paid down by plugin deletion
|
||||
```
|
||||
|
||||
Comment-only deletion does not count as cleanup. The previous budget pass was
|
||||
too generous because it included restored QQBot explanatory comments; this
|
||||
document tracks executable/docs/test code movement only.
|
||||
|
||||
Re-measure after each cleanup wave:
|
||||
|
||||
```sh
|
||||
base=$(git merge-base HEAD origin/main)
|
||||
git diff --shortstat "$base"
|
||||
git diff --numstat "$base" -- src/channels/message-access src/plugin-sdk extensions | sort -nr -k1 | head -n 80
|
||||
pnpm lint:extensions:no-deprecated-channel-access
|
||||
```
|
||||
|
||||
## Diagnosis
|
||||
|
||||
The first pass added the shared ingress kernel, then left too much plugin-local
|
||||
authorization beside it:
|
||||
|
||||
```text
|
||||
platform facts
|
||||
-> shared ingress state and decision
|
||||
-> plugin-local DTO or legacy projection
|
||||
-> plugin-local if/else ladder
|
||||
```
|
||||
|
||||
That duplicates the model. Core production grew by about 3,376 lines, while
|
||||
bundled plugin production is 1,240 lines smaller. That is better than the first
|
||||
pass, but it is not inside the minimum budget. The fix remains deletion-first:
|
||||
|
||||
- delete plugin DTOs that only rename ingress fields
|
||||
- delete tests that only assert wrapper shape
|
||||
- add core helpers only when the same patch deletes bundled plugin code
|
||||
- keep old SDK compatibility in SDK/core shims only
|
||||
- repack core after wrapper deletion exposes the stable shape
|
||||
|
||||
## Hotspots
|
||||
|
||||
Positive bundled production files that still need to shrink:
|
||||
|
||||
```text
|
||||
extensions/telegram/src/ingress.ts +126
|
||||
extensions/discord/src/monitor/dm-command-auth.ts +101
|
||||
extensions/signal/src/monitor/access-policy.ts +92
|
||||
extensions/feishu/src/policy.ts +85
|
||||
extensions/slack/src/monitor/auth.ts +64
|
||||
extensions/googlechat/src/monitor-access.ts +59
|
||||
extensions/nextcloud-talk/src/inbound.ts +51
|
||||
extensions/matrix/src/matrix/monitor/access-state.ts +49
|
||||
extensions/irc/src/inbound.ts +44
|
||||
extensions/imessage/src/monitor/inbound-processing.ts +36
|
||||
extensions/qa-channel/src/inbound.ts +34
|
||||
extensions/qqbot/src/bridge/sdk-adapter.ts +33
|
||||
extensions/tlon/src/monitor/utils.ts +30
|
||||
extensions/twitch/src/access-control.ts +22
|
||||
extensions/qqbot/src/engine/commands/slash-command-handler.ts +20
|
||||
extensions/telegram/src/bot-handlers.runtime.ts +19
|
||||
```
|
||||
|
||||
The branch is not inside the minimum budget yet. The remaining review-relevant
|
||||
work should delete repeated authorization flow, turn scaffolding, or wrapper
|
||||
tests before adding another core abstraction.
|
||||
|
||||
## Current Code Read
|
||||
|
||||
The healthy core seam already exists in `src/channels/message-access/runtime.ts`:
|
||||
it owns identity adapters, effective allowlists, pairing-store reads, route
|
||||
descriptors, command/event presets, access groups, and the final resolved
|
||||
`ResolvedChannelMessageIngress` projection.
|
||||
|
||||
The remaining growth is mostly plugin glue layered on top of that seam:
|
||||
|
||||
- `extensions/telegram/src/ingress.ts` wraps core decisions in Telegram-specific
|
||||
command/event helpers, then call sites still pass precomputed normalized
|
||||
allowlists and owner lists.
|
||||
- `extensions/discord/src/monitor/dm-command-auth.ts`,
|
||||
`extensions/feishu/src/policy.ts`, `extensions/googlechat/src/monitor-access.ts`,
|
||||
and `extensions/matrix/src/matrix/monitor/access-state.ts` still keep
|
||||
local policy DTOs or legacy decision names beside ingress.
|
||||
- `extensions/signal/src/monitor/access-policy.ts` correctly keeps Signal
|
||||
identity normalization and pairing replies local, but still has a wrapper
|
||||
seam that should collapse into direct ingress consumption.
|
||||
- `extensions/nextcloud-talk/src/inbound.ts`, `extensions/irc/src/inbound.ts`,
|
||||
`extensions/qa-channel/src/inbound.ts`, `extensions/zalo/src/monitor.ts`, and
|
||||
`extensions/zalouser/src/monitor.ts` still repeat route/envelope/turn
|
||||
assembly that can move to shared turn helpers outside the ingress kernel.
|
||||
|
||||
Conclusion: moving more code into core is only useful if it deletes these
|
||||
plugin wrapper layers in the same patch. Adding another abstraction while
|
||||
leaving wrapper returns in place repeats the mistake.
|
||||
|
||||
## Boundary
|
||||
|
||||
Core owns generic policy:
|
||||
|
||||
- allowlist normalization and matching
|
||||
- access-group expansion and diagnostics
|
||||
- pairing-store DM allowlist reads
|
||||
- route, sender, command, event, and activation gates
|
||||
- admission mapping: dispatch, drop, skip, observe, pairing
|
||||
- redacted state, decisions, diagnostics, and SDK compatibility projections
|
||||
- reusable generic descriptors for identity, route, command, event, activation,
|
||||
and outcomes
|
||||
|
||||
Plugins own transport facts and side effects:
|
||||
|
||||
- webhook/socket/request authenticity
|
||||
- platform identity extraction and API lookups
|
||||
- channel-specific policy defaults
|
||||
- pairing challenge delivery, replies, acks, reactions, typing, media, history,
|
||||
setup, doctor, status, logs, and user-facing copy
|
||||
|
||||
Core must stay channel-agnostic: no Discord, Slack, Telegram, Matrix, room,
|
||||
guild, space, API client, or plugin-specific default in
|
||||
`src/channels/message-access`.
|
||||
|
||||
## Acceptance Rule
|
||||
|
||||
Every new core helper must delete bundled plugin production code immediately.
|
||||
|
||||
```text
|
||||
one bundled caller reject; keep plugin-local
|
||||
two bundled callers accept only if plugin production LOC drops
|
||||
three or more callers plugin deletion must be at least 2x new core LOC
|
||||
compatibility-only helper SDK/core shim only; never bundled hot paths
|
||||
```
|
||||
|
||||
Stop and redesign if:
|
||||
|
||||
- plugin production LOC increases
|
||||
- tests grow faster than production shrinks
|
||||
- a bundled hot path returns a DTO that only renames `ResolvedChannelMessageIngress`
|
||||
- a core helper needs a channel id, platform object, API client, or
|
||||
channel-specific default
|
||||
|
||||
## Work Packages
|
||||
|
||||
1. Freeze the budget.
|
||||
Put LOC in the PR, keep deprecated-ingress lint green, and include before/after
|
||||
LOC in cleanup commits.
|
||||
|
||||
2. Delete thin DTO seams.
|
||||
Replace plugin-local wrapper returns with `ResolvedChannelMessageIngress`,
|
||||
`senderAccess`, `commandAccess`, `routeAccess`, or `ingress` directly. Start
|
||||
with QQBot, Telegram, Slack, Discord, Signal, Feishu, Matrix, iMessage, and
|
||||
Tlon. Delete wrapper-shape tests; keep behavior tests.
|
||||
|
||||
3. Add outcome classification only with deletions.
|
||||
A generic classifier may expose `dispatch`, `pairing-required`,
|
||||
`skip-activation`, `drop-command`, `drop-route`, `drop-sender`, and
|
||||
`drop-ingress`. It must derive from the decision graph, not reason strings,
|
||||
and migrate at least three plugins in the same patch.
|
||||
|
||||
4. Add route descriptor builders only with deletions.
|
||||
Generic route target and route sender helpers are acceptable only if they
|
||||
immediately shrink route-heavy plugins: Google Chat, IRC, Microsoft Teams,
|
||||
Nextcloud Talk, Mattermost, Slack, Zalo, and Zalo Personal.
|
||||
|
||||
5. Add command/event presets only with deletions.
|
||||
Centralize text-command, native-command, callback, and origin-subject shapes.
|
||||
Command consumers must default to unauthorized when no command gate ran;
|
||||
events must not start pairing.
|
||||
|
||||
6. Add identity presets only where they remove boilerplate.
|
||||
Stable-id, stable-id-plus-aliases, phone/e164, and multi-identifier helpers
|
||||
are allowed when raw values enter only adapter input and redacted state keeps
|
||||
opaque ids/counts.
|
||||
|
||||
7. Share authorized turn assembly.
|
||||
Outside the ingress kernel, remove repeated route/envelope/context/reply
|
||||
scaffolding from QA Channel, IRC, Nextcloud Talk, Zalo, and Zalo Personal.
|
||||
Core may own route/session/envelope/dispatch sequencing; plugins keep
|
||||
delivery and channel-specific context.
|
||||
|
||||
8. Quarantine compatibility.
|
||||
Deprecated SDK helpers stay source-compatible, but bundled hot paths must not
|
||||
import deprecated ingress or command-auth facades. Compatibility tests should
|
||||
use fake third-party plugins, not bundled-plugin internals.
|
||||
|
||||
9. Repack core.
|
||||
After wrapper deletion, collapse one-use modules, remove unused exports, move
|
||||
compatibility projection out of hot paths, and keep focused tests for identity,
|
||||
route, command/event, activation, access groups, and compatibility shims.
|
||||
|
||||
## Deletion Waves
|
||||
|
||||
Run these in order. Each wave must lower bundled production LOC.
|
||||
|
||||
1. Wrapper collapse, expected plugin delta: -400 to -600.
|
||||
Replace plugin-local `resolveXAccess`, `resolveXCommandAccess`, and
|
||||
`accessFromIngress` result types with direct reads from
|
||||
`ResolvedChannelMessageIngress`. First targets: Discord DM command auth,
|
||||
Feishu policy, Matrix access state, Telegram ingress, Signal access policy,
|
||||
QQBot SDK adapter.
|
||||
|
||||
2. Shared outcome helpers, expected plugin delta: -200 to -350.
|
||||
Add one generic classifier only if it deletes repeated
|
||||
`shouldBlockControlCommand`, pairing, activation skip, route block, and sender
|
||||
block ladders across at least three plugins.
|
||||
|
||||
3. Route descriptor builders, expected plugin delta: -200 to -350.
|
||||
Move repeated route target and route sender descriptor assembly into core
|
||||
helpers. First targets: Google Chat, IRC, Microsoft Teams, Nextcloud Talk,
|
||||
Mattermost, Slack, Zalo, Zalo Personal.
|
||||
|
||||
4. Turn assembly sharing, expected plugin delta: -250 to -450.
|
||||
Use common route/session/envelope/dispatch sequencing for simple inbound
|
||||
plugins. First targets: QA Channel, IRC, Nextcloud Talk, Zalo, Zalo Personal.
|
||||
|
||||
5. Core repack, expected core delta: -300 to -700.
|
||||
After plugins consume runtime projections directly, delete one-use modules,
|
||||
merge tiny files back into `runtime.ts` or focused siblings, and keep SDK
|
||||
compatibility files separate from bundled hot paths.
|
||||
|
||||
6. Test pruning, expected test delta: -300 to -600.
|
||||
Delete tests that only assert removed wrapper shapes. Keep behavior tests for
|
||||
command denial, group fallback, origin-subject matching, activation skip,
|
||||
access groups, pairing, and redaction.
|
||||
|
||||
Expected minimum landing shape after these waves:
|
||||
|
||||
```text
|
||||
plugin production <= -1,500
|
||||
core production about +1,800 to +2,200 before final repack
|
||||
tests <= +500
|
||||
total <= +2,000
|
||||
```
|
||||
|
||||
## Do Not Move
|
||||
|
||||
Do not move platform config defaults, setup UX, doctor/fix copy, API lookups,
|
||||
Slack owner-presence checks, Matrix alias/verification handling, Telegram
|
||||
callback parsing, command syntax parsing, native command registration, reaction
|
||||
payload parsing, pairing replies, command replies, acks, typing, media, history,
|
||||
or logs.
|
||||
|
||||
## Verification
|
||||
|
||||
Targeted local loop:
|
||||
|
||||
```sh
|
||||
pnpm lint:extensions:no-deprecated-channel-access
|
||||
pnpm test src/channels/message-access/message-access.test.ts src/plugin-sdk/channel-ingress-runtime.test.ts src/plugin-sdk/access-groups.test.ts
|
||||
pnpm test extensions/<changed-plugin>/src/...
|
||||
pnpm plugin-sdk:api:check
|
||||
pnpm config:docs:check
|
||||
pnpm check:docs
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Use Testbox for broad changed gates/full-suite proof once the LOC trend is
|
||||
inside budget.
|
||||
|
||||
Each work package records:
|
||||
|
||||
- before/after LOC by category
|
||||
- deleted plugin wrappers
|
||||
- new core helper LOC, if any
|
||||
- targeted tests run
|
||||
- remaining hotspot list
|
||||
|
||||
## Exit Criteria
|
||||
|
||||
- bundled production imports no deprecated channel-access or command-auth facades
|
||||
- compatibility code is isolated to SDK/core seams
|
||||
- bundled plugins consume ingress projections or generic outcomes directly
|
||||
- plugin production LOC is at least 1,500 net negative against `origin/main`
|
||||
- core production LOC is `<= +1,500`, or any excess is paid for while total
|
||||
stays `<= +2,000`
|
||||
- representative tests cover redaction, route, command/event, activation,
|
||||
access-group, and channel-specific fallback behavior
|
||||
Reference in New Issue
Block a user