feat(app-boot): register cordis:group beside cordis:include

A composition that shares one `isolate` realm across rows needs a
`cordis:group` row, and a preset living outside this workspace — the
authored ones under the Harness home — cannot resolve
`@cordisjs/plugin-group` by name: Node's upward `node_modules` walk
never reaches the harness from there. Registering it as a loader builtin
beside `cordis:include` loads both through the ambient module pipeline
instead.

Record it in the preset Agent Note, which leans on the realm vocabulary
without saying where the group row comes from, and drop the preset
README's limitation claiming this builtin is unavailable — it described
the state this change ends.

The test's assertion had a vacuous escape: `provide` mints the root
symbol unconditionally, so the `rootKey === undefined` disjunct could
never hold and the comment claiming the root realm never learned the
name was wrong. Pin both halves — the symbol exists, nothing is stored
under it — and clean up the global the fixture writes.
This commit is contained in:
Yichen Jiang
2026-08-06 11:57:00 +08:00
parent 065257addb
commit e27d38efd6
15 changed files with 82 additions and 14 deletions

View File

@@ -8,9 +8,8 @@ import { mkdtempSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
import type { Context } from 'cordis'
import { Context } from 'cordis'
import type { Include } from '@cordisjs/plugin-include'
import { Group } from '@cordisjs/plugin-loader'
import { boot } from '../src/index.ts'
const NAME = 'dsh-test-bin'
@@ -219,8 +218,10 @@ describe('loader tree replacement', () => {
})
it('stops and restores descendants when an ancestor group is disabled and re-enabled', async () => {
// No manual builtin registration: `boot()` supplies `cordis:group` beside
// `cordis:include`, which is what lets a composition give one `isolate`
// realm to a provider and its consumers together.
const { ctx, dir, include } = await bootTree('- id: noop\n name: ./noop.mjs\n')
ctx.loader.builtins.group = Group
try {
const config = (disabled: boolean) => [
'- id: parent',
@@ -253,7 +254,6 @@ describe('loader tree replacement', () => {
const { ctx } = await bootTree('- id: noop\n name: ./noop.mjs\n', {
'movable.mjs': plugin('movablePlugin', 'if (config.fail) throw new Error("candidate config failed")'),
})
ctx.loader.builtins.group = Group
try {
const groupId = await ctx.loader.create({ name: 'cordis:group', group: true, config: [] })
const targetId = await ctx.loader.create({ name: './movable.mjs', config: { fail: false } })
@@ -386,3 +386,46 @@ describe('include patches layered over one base', () => {
}
})
})
describe('shipped builtins', () => {
it('lets a booted composition share one isolate realm across a group of rows', async () => {
// The reason `boot()` registers `cordis:group`: a composition — notably an
// agent preset living outside this workspace, which cannot resolve
// `@cordisjs/plugin-group` by name — gives a provider and its consumer one
// named realm so the service stays out of the root realm while remaining
// visible to the rows that need it.
const { ctx } = await bootTree([
'- id: realm',
' name: cordis:group',
' isolate:',
' demoRealmSvc: true',
' config:',
' - id: provider',
' name: ./provider.mjs',
' - id: consumer',
' name: ./consumer.mjs',
'',
].join('\n'), {
'provider.mjs': 'export const name = "provider"\n'
+ 'export function apply(ctx) { ctx.effect(() => ctx.reflect.provide("demoRealmSvc", { tag: "realm" })) }\n',
'consumer.mjs': 'export const name = "consumer"\n'
+ 'export const inject = ["demoRealmSvc"]\n'
+ 'export function apply(ctx) { globalThis.__REALM_SEEN__ = ctx.get("demoRealmSvc").tag }\n',
})
try {
expect((globalThis as { __REALM_SEEN__?: string }).__REALM_SEEN__).toBe('realm')
// `provide` mints the root symbol unconditionally (cordis `reflect.ts`),
// so the name IS in the root realm — pinned here because it is the half
// that looks like the claim and is not. The claim is the other half: no
// implementation is stored under that symbol, so the root realm cannot
// resolve the service and a second composition mounting the same rows
// cannot collide with this one.
const rootKey = ctx.root[Context.isolate].demoRealmSvc
expect(rootKey).toBeDefined()
expect(ctx.reflect.store[rootKey!]).toBeUndefined()
} finally {
delete (globalThis as { __REALM_SEEN__?: string }).__REALM_SEEN__
await ctx.fiber.dispose()
}
})
})