Files
deepseek-harness/packages/self-modification/tool-cordis/tests/cordis-lifecycle.spec.ts
imccyu ec601ca13d build(vendor): rescope the vendored Cordis packages into @deepseek-ai
Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it
prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`,
`verify-translation-pairing --write` for the touched bilingual pairs,
`gen-doc-graphs`, and one typert snapshot whose ids embed character offsets.
`pnpm run rescope-vendor --check` verifies the result.

Renames nine vendored packages (cordis, cosmokit, schemastery and the six
@cordisjs plugins) and every reference that resolves them: manifest names and
dependency keys, module specifiers including declare-module merges, cordis.yml
plugin names, tsconfig paths, every Markdown fence, and `docs/` prose.
Directory names, upstream versions, and dependency ranges are unchanged, so
vendor/README.md still reads as an upstream snapshot; its manifest table gains
an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed
at each fork's origin.

The tutorial tier follows the rename end to end: its yaml fences named plugins
the Loader can no longer resolve, its `ts ignore-check` fences disagreed with
the compiled fences beside them, and its prose quoted both. The contracts that
told readers to keep upstream names — the root convention and the vendoring
cookbook's tree comment and manifest invariant — now say to rescope instead.

Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle
purity gate now names the vendored libraries a browser bundle inlines, and the
files where a bare `cordis` is an agent-preset id keep that product data.
2026-08-10 22:04:13 +08:00

288 lines
9.2 KiB
TypeScript

import { Context, CordisError, FiberState, type Fiber } from '@deepseek-ai/cordis'
import { describe, expect, it } from 'vitest'
/**
* Direct regressions for the vendored Cordis ownership substrate used by
* tool-cordis's dynamic plugin tree and every other harness plugin.
*/
describe('Cordis effect ownership', () => {
it('makes an effect visible to a reentrant owner restart and awaits setup plus cleanup', async () => {
const ctx = new Context()
const setupGate = Promise.withResolvers<undefined>()
const cleanupGate = Promise.withResolvers<undefined>()
const cleanupStarted = Promise.withResolvers<undefined>()
let restarted!: Promise<void>
let setupFinished = false
let cleanupFinished = false
ctx.effect(async () => {
restarted = ctx.fiber.restart()
await setupGate.promise
setupFinished = true
return async () => {
cleanupStarted.resolve(undefined)
await cleanupGate.promise
cleanupFinished = true
}
}, 'reentrant-restart')
let settled = false
void restarted.then(() => { settled = true })
await Promise.resolve()
expect(settled).toBe(false)
setupGate.resolve(undefined)
await cleanupStarted.promise
expect(setupFinished).toBe(true)
await Promise.resolve()
expect(settled).toBe(false)
cleanupGate.resolve(undefined)
await restarted
expect(cleanupFinished).toBe(true)
expect(ctx.fiber.getEffects()).toEqual([])
})
it('rolls back collected cleanup and its owner-list entry when setup throws synchronously', () => {
const ctx = new Context()
let cleanups = 0
expect(() => ctx.effect(function* () {
yield () => { cleanups += 1 }
throw new Error('setup failed')
}, 'throwing-setup')).toThrow('setup failed')
expect(cleanups).toBe(1)
expect(ctx.fiber.getEffects()).toEqual([])
})
it('makes a reentrant owner restart await asynchronous rollback after synchronous setup failure', async () => {
const ctx = new Context()
const cleanupGate = Promise.withResolvers<undefined>()
const cleanupStarted = Promise.withResolvers<undefined>()
let restarted!: Promise<void>
expect(() => ctx.effect(function* () {
yield async () => {
cleanupStarted.resolve(undefined)
await cleanupGate.promise
}
restarted = ctx.fiber.restart()
throw new Error('setup failed after restart')
}, 'reentrant-throw')).toThrow('setup failed after restart')
await cleanupStarted.promise
let settled = false
void restarted.then(() => { settled = true })
await Promise.resolve()
expect(settled).toBe(false)
cleanupGate.resolve(undefined)
await restarted
expect(ctx.fiber.getEffects()).toEqual([])
})
it('keeps ordinary teardown synchronous and the public disposer single-shot', () => {
const ctx = new Context()
let cleanups = 0
const dispose = ctx.effect(() => () => { cleanups += 1 }, 'sync-effect')
expect(dispose()).toBeUndefined()
expect(cleanups).toBe(1)
expect(dispose()).toBeUndefined()
expect(cleanups).toBe(1)
expect(ctx.fiber.getEffects()).toEqual([])
})
it('rejects cleanup-time registration while a restart is unloading', async () => {
const ctx = new Context()
let registrationError: unknown
ctx.effect(() => () => {
try {
ctx.effect(() => () => {}, 'too-late')
} catch (error) {
registrationError = error
}
}, 'restart-cleanup')
await ctx.fiber.restart()
expect(registrationError).toBeInstanceOf(CordisError)
expect((registrationError as CordisError).code).toBe('INACTIVE_EFFECT')
expect(ctx.fiber.state).toBe(FiberState.ACTIVE)
expect(ctx.fiber.getEffects()).toEqual([])
})
it('keeps effect registration legal while child fibers are PENDING and LOADING', async () => {
const ctx = new Context()
let pendingCleanup = false
let loadingCleanup = false
ctx.on('internal/plugin', (fiber) => {
if (fiber.name !== 'state-probe' || fiber.uid === null) return
expect(fiber.state).toBe(FiberState.PENDING)
fiber.ctx.effect(() => () => { pendingCleanup = true }, 'pending-effect')
})
const fiber = await ctx.plugin({
name: 'state-probe',
apply(inner) {
expect(inner.fiber.state).toBe(FiberState.LOADING)
inner.effect(() => () => { loadingCleanup = true }, 'loading-effect')
},
})
await fiber.dispose()
expect(pendingCleanup).toBe(true)
expect(loadingCleanup).toBe(true)
})
it('resolves dependencies that internal/plugin adds before child activation', async () => {
const ctx = new Context()
ctx.provide('late-inject', {})
let applyCalls = 0
ctx.on('internal/plugin', (fiber) => {
if (fiber.name !== 'loader-shaped' || fiber.uid === null) return
fiber.inject['late-inject'] = {}
})
const fiber = await ctx.plugin({
name: 'loader-shaped',
apply() {
applyCalls += 1
},
})
expect(applyCalls).toBe(1)
expect(fiber.state).toBe(FiberState.ACTIVE)
})
})
describe('Cordis child publication ownership', () => {
it('rolls back parent and runtime ownership when internal/plugin publication throws', () => {
const ctx = new Context()
const plugin = { name: 'publication-failure', apply() {} }
ctx.on('internal/plugin', (fiber) => {
if (fiber.name === plugin.name) throw new Error('publication failed')
})
expect(() => ctx.plugin(plugin)).toThrow('publication failed')
expect(ctx.registry.has(plugin)).toBe(false)
})
it('contains teardown notification failures so ownership cleanup and peers complete', async () => {
const ctx = new Context()
const errors: unknown[] = []
ctx.logger.error = ((error: unknown) => { errors.push(error) }) as typeof ctx.logger.error
const observed: string[] = []
ctx.on('internal/plugin', (fiber) => {
if (fiber.name === 'contained-teardown' && fiber.uid === null) {
throw new Error('broken teardown observer')
}
})
ctx.on('internal/plugin', (fiber) => {
if (fiber.name === 'contained-teardown' && fiber.uid === null) observed.push('disposed')
})
const child = await ctx.plugin({ name: 'contained-teardown', apply() {} })
await expect(child.dispose()).resolves.toBeUndefined()
expect(observed).toEqual(['disposed'])
expect(errors).toHaveLength(1)
expect(errors[0]).toEqual(expect.objectContaining({ message: 'broken teardown observer' }))
expect(child.uid).toBeNull()
})
it('makes a LOADING parent join child cleanup started before its unload snapshot', async () => {
const ctx = new Context()
const cleanupGate = Promise.withResolvers<undefined>()
const cleanupStarted = Promise.withResolvers<undefined>()
let ownerFiber!: Fiber
let ownerDisposal!: Promise<void>
let childDisposal!: Promise<void>
let childFiber!: Fiber
ctx.on('internal/plugin', (fiber) => {
if (fiber.name !== 'loading-child' || fiber.uid === null) return
childFiber = fiber
fiber.ctx.effect(() => async () => {
cleanupStarted.resolve(undefined)
await cleanupGate.promise
}, 'loading-child-cleanup')
ownerDisposal = ownerFiber.dispose()
childDisposal = Promise.resolve(fiber.dispose())
})
const ownerMount = ctx.plugin({
name: 'loading-owner',
apply(inner) {
ownerFiber = inner.fiber
inner.plugin({ name: 'loading-child', apply() {} })
},
})
await cleanupStarted.promise
let ownerSettled = false
void ownerDisposal.then(() => { ownerSettled = true })
await Promise.resolve()
expect(ownerSettled).toBe(false)
cleanupGate.resolve(undefined)
await Promise.all([ownerDisposal, childDisposal, ownerMount])
expect(childFiber.uid).toBeNull()
expect(ownerFiber.uid).toBeNull()
})
it('lets parent disposal during internal/plugin await the unpublished child to quiescence', async () => {
const ctx = new Context()
let ownerCtx!: Context
const owner = await ctx.plugin({
name: 'owner',
apply(inner) {
ownerCtx = inner
},
})
const cleanupGate = Promise.withResolvers<undefined>()
const cleanupStarted = Promise.withResolvers<undefined>()
let cleanupFinished = false
let childApplyCalls = 0
let parentDisposal!: Promise<void>
ctx.on('internal/plugin', (fiber) => {
if (fiber.name !== 'child' || fiber.uid === null) return
expect(fiber.state).toBe(FiberState.PENDING)
fiber.ctx.effect(() => async () => {
cleanupStarted.resolve(undefined)
await cleanupGate.promise
cleanupFinished = true
}, 'pending-child-cleanup')
})
ctx.on('internal/plugin', (fiber) => {
if (fiber.name !== 'child' || fiber.uid === null) return
parentDisposal = owner.dispose()
})
const child = ownerCtx.plugin({
name: 'child',
apply() {
childApplyCalls += 1
},
})
await cleanupStarted.promise
let settled = false
void parentDisposal.then(() => { settled = true })
await Promise.resolve()
expect(settled).toBe(false)
cleanupGate.resolve(undefined)
await parentDisposal
expect(cleanupFinished).toBe(true)
expect(childApplyCalls).toBe(0)
expect(child.uid).toBeNull()
expect(child.state).toBe(FiberState.DISPOSED)
})
})