Repository navigation
refactor(contracts): plugin-owned contracts, part 3 (channels, connector accounts) - #53
Merged
Merged
Conversation
oauthGrantKey, connectorAdapterId, connectorSecretKey and connectorStoreKey produce persisted keys (OAuth grants, adapter ids, secret-store and plugin store keys). The wire snapshot covers schemas only, so pin every format, the default-account legacy keys and the accepted account-id shape before any helper moves.
The ./contract export check read tsconfig.main.json unconditionally and crashed with ENOENT for main-only plugins such as channel-mock, which build with tsconfig.json. Read the tsconfig named by the build script (defaulting to tsconfig.main.json), accept src/*.ts and src/**/*.ts globs, and report a missing tsconfig as a failure instead of throwing.
Moves the channel-specific commands and status schemas verbatim from the @borg/contracts root into plugins/channel-<name>/src/contract.ts, each exported as ./contract: - channel-mock: mockChannelInject, mockChannelSend - channel-discord: discordChannelStatusSchema .. discordChannelDisconnect - channel-slack: slackSocketStateSchema .. slackChannelDisconnect - channel-imap: imapChannelInject - channel-m365: m365ChannelStatusSchema .. m365ChannelInject - channel-google: googleChannelStatusSchema .. googleChannelInject channelInboundMessage, channelAttachmentHandleSchema and KERNEL_ONLY_EVENT_IDS stay in the root: the kernel publishes and guards that event. There is no shared channel plugin; the contracts import the connector account schemas from @borg/contracts. packages/contracts/test/slack-channel-commands.test.ts moves to plugins/channel-slack/test/contract.test.ts unchanged apart from its import. The discord and slack dependency pins now include zod. Command ids, timeouts and schema digests are unchanged; the wire snapshot is untouched.
Moves the account-management helpers verbatim from the @borg/contracts root to packages/contracts/src/connector-accounts.ts, published as @borg/contracts/connector-accounts: slugifyConnectorAccountName, allocateConnectorAccountId, MAX_CONNECTOR_ACCOUNTS, CONNECTOR_ACCOUNT_NAME_MAX, connectorAccountNameSchema and connectorCommandInputSchema (with ConnectorCommandInput). The account id and every persisted key format stay in the root, so one parser owns them: connectorAccountIdSchema, CONNECTOR_ACCOUNT_ID_MAX, CONNECTOR_ACCOUNT_ID_PATTERN, DEFAULT_CONNECTOR_ACCOUNT_ID, isUnprefixedConnectorAccountId, parsedConnectorAccountId, oauthGrantKey, connectorAdapterId, connectorSecretKey and connectorStoreKey. The kernel still needs only the root (oauthGrantKey). The subpath imports the id schema and constants from the root; the root never imports the subpath, which check-plugin-boundaries enforces. Channel plugins and coinbase import the moved names from the subpath. The key-format pin test is unchanged and passes; the wire snapshot is untouched.
…ability subpaths The calendar, contacts and drive tool schemas are implemented by both channel-google and channel-m365, and the kernel never uses them. Like web-search, each moves verbatim out of the root export into its own module: @borg/contracts/calendar, @borg/contracts/contacts and @borg/contracts/drive (DRIVE_ITEM_ID_PATTERN and MAX_DRIVE_TEXT_CHARS go with drive; the private contactEmailSchema goes with contacts). Each imports connectorAccountIdSchema from the root. Schema digests are unchanged; the wire snapshot is untouched.
danielgerlag
force-pushed
the
refactor/contracts-split-3
branch
from
September 26, 2026 10:26
d21260d to
91a847e
Compare
4 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #31
Part 3 of the contracts split. The channel contracts move into their plugins, and connector account management moves to a subpath, following the connector-accounts refinement from review. The calendar, contacts and drive schemas become capability subpaths.
Commits
test(contracts): pin connector key formats before the connector split, committed before anything moves.packages/contracts/test/connector-key-formats.test.tspins the persisted formats ofoauthGrantKey,connectorAdapterId,connectorSecretKeyandconnectorStoreKey: the default account keeps the legacy unprefixed keys, other accounts use<plugin>.<id>,<id>.<leaf>and<id>/<leaf>.build: check the contract include against the plugin's build tsconfig./contractcheck readtsconfig.main.jsonunconditionally. It crashed with ENOENT on channel-mock, which is main-only and builds withtsconfig.json.src/*.tsandsrc/**/*.tsglobs, and reports a missing tsconfig as a failure.refactor(channels): move channel contracts into each channel plugin: mock, discord, slack, imap, m365 and google each getsrc/contract.ts, exported as./contract.channelInboundMessage,channelAttachmentHandleSchemaandKERNEL_ONLY_EVENT_IDSstay in the root, because the kernel publishes and guards that event. Their behavior is unchanged.plugins/channel-slack/test/contract.test.ts.zod.refactor(contracts): move connector account management to a subpath: the new@borg/contracts/connector-accountssubpath.slugifyConnectorAccountName,allocateConnectorAccountId,MAX_CONNECTOR_ACCOUNTS,CONNECTOR_ACCOUNT_NAME_MAX,connectorAccountNameSchema,connectorCommandInputSchema(withConnectorCommandInput).oauthGrantKey,isUnprefixedConnectorAccountId,parsedConnectorAccountId,connectorAccountIdSchema,DEFAULT_CONNECTOR_ACCOUNT_ID,connectorSecretKey,connectorStoreKey,connectorAdapterId.CONNECTOR_ACCOUNT_ID_MAXandCONNECTOR_ACCOUNT_ID_PATTERNalso stay, becauseconnectorAccountIdSchemais built from them.docs/architecture.mddescribes the split.refactor(contracts): move calendar, contacts and drive schemas to capability subpaths@borg/contracts/calendar,/contactsand/drive.web-search.All moves are verbatim. I checked this by diffing the lines removed from
packages/contracts/src/index.tsagainst the new module bodies; only import lines differ. Command ids, timeouts and schema digests are unchanged, andapps/desktop/test/wire-contract.snapshot.txtis untouched.Contracts root size (
packages/contracts/src/index.ts)Planted violations
I planted each of these in the new code, ran
node scripts/check-plugin-boundaries.mjs, saw the failure, and reverted it:export * from "./connector-accounts"in the root indexpackages/contracts/src/index.ts reaches packages/contracts/src/connector-accounts.ts, which backs the ./connector-accounts subpath; the root export must not include capability schemas./drive... reaches packages/contracts/src/drive.ts, which backs the ./drive subpath ...@borg/contracts/connector-accountspackages/kernel/src/a2a-service.ts imports @borg/contracts/connector-accounts; packages/kernel may import @borg/contracts only as @borg/contracts./contractexport removed@borg/plugin-channel-mock has src/contract.ts but does not export ./contracttsconfig.jsoninclude narrowed tosrc/main.ts@borg/plugin-channel-mock exports ./contract but tsconfig.json include does not contain "src/contract.ts"defineCommand(...)inplugins/channel-slack/src/runtime.tsplugins/channel-slack/src/runtime.ts calls defineCommand/defineEvent; only packages/contracts/src and plugins/*/src/contract.ts declare bus contractsplugin dependency cycle: @borg/plugin-channel-google -> @borg/plugin-channel-m365 -> @borg/plugin-channel-google(plus the unused-dependency failures)@borg/plugin-channel-m365/main... may import @borg/plugin-channel-m365 only as @borg/plugin-channel-m365/contractimport "./config"inchannel-slack/src/contract.tssrc/contract.ts may import only zod, @borg/contracts, @borg/contracts/<subpath>, or @borg/plugin-<name>/contractOne gap turned up: importing
defineCommand as dand callingd(...)is not caught. I added it to #45 and did not fix it here.Gates (Node 24, Linux), rebased on main at af40cc5 (#46)
After the rebase, the lockfile diff against main is only the six
zodimporter entries, and every planted violation above fails the same way.pnpm install --frozen-lockfile: OK. The lockfile only addszodfor the six channel packages.pnpm typecheck: pass.pnpm test: 138 files, 933 tests passed (main plus the new pin test file, 4 tests).xvfb-run pnpm test:e2e: 44 passed, 2 skipped, 0 failed.Left for slice 4: mcp/mcp-apps (with
mcp-app-csp.ts),embeddedContentRegistered, feedback, andborg.a2a.getStatus. The #45 items and #48 are out of scope.