test(deps): enforce runtime dependency ownership

This commit is contained in:
imccyu 2026-08-30 02:29:52 +08:00
parent 9135a13a8b
commit 34bdc81b47
8 changed files with 100 additions and 46 deletions

View file

@ -95,6 +95,7 @@ describe('client bundle purity gate', () => {
expect(resolveId('@deepseek-ai/dsh-session/surface')).toBeNull()
expect(resolveId('@deepseek-ai/dsh-brand')).toBeNull()
expect(resolveId('@deepseek-ai/dsh-deque')).toBeNull()
expect(resolveId('@deepseek-ai/dsh-util-values')).toBeNull()
expect(resolveId('@deepseek-ai/dsh-token-meter/client')).toBeNull()
expect(() => resolveId('@deepseek-ai/dsh-token-meter')).toThrow(/purity/)
expect(() => resolveId('@deepseek-ai/dsh-token-meter/client/internal')).toThrow(/purity/)

View file

@ -76,7 +76,7 @@ const PACKAGE_LIBRARIES: Readonly<Record<string, string>> = {
'packages/typert/generator': 'Build-time generator run outside any agent runtime.',
'packages/typert/protocol': 'Compiler-independent protocol declarations.',
'packages/util/atomic-write': 'Zero-dependency filesystem write utility.',
'packages/util/brand': 'Type-only branding primitive erased at compile time.',
'packages/util/brand': 'Stateless nominal-string and canonical-key constructors.',
'packages/util/crypto': 'Zero-dependency identifier minting utility.',
'packages/util/deque': 'Zero-dependency circular deque utility.',
'packages/util/home-paths': 'Zero-dependency harness-home path resolver.',
@ -85,6 +85,7 @@ const PACKAGE_LIBRARIES: Readonly<Record<string, string>> = {
'packages/util/output-retention': 'Zero-dependency retention utility.',
'packages/util/time': 'Zero-dependency time-zone canonicalization utility.',
'packages/util/timeout': 'Zero-dependency timeout utility.',
'packages/util/values': 'Stateless lossless-JSON and immutable-value helpers.',
'packages/util/workspace-path': 'Zero-dependency Workspace path formatter.',
}

View file

@ -493,7 +493,9 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
SubagentStartRequest: 'subagent.md',
AssembleContext: 'system-prompt.md',
PromptContext: 'system-prompt.md',
PromptContextOrderName: 'system-prompt.md',
PromptSection: 'system-prompt.md',
PromptSectionOrderName: 'system-prompt.md',
SystemPrompt: 'system-prompt.md',
ToolProviderResult: 'system-prompt.md',
JobDoneListener: 'jobs.md',
@ -533,7 +535,9 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
ToolRestriction: 'tools.md',
ToolSchema: 'tools.md',
SettingsNamespace: 'settings.md',
SettingsNamespaceInput: 'settings.md',
SettingsRegisterOptions: 'settings.md',
SettingsSectionHooks: 'settings.md',
SettingsScope: 'settings.md',
SettingsDescriptor: 'settings.md',
SettingsDescribeValue: 'settings.md',

View file

@ -29,29 +29,28 @@ const CONFIGURATION_ONLY_DEV_DEPENDENCIES = {
'@deepseek-ai/dsh-client-ui-tool': ['@deepseek-ai/dsh-api-remotes'],
} as const satisfies Readonly<Record<string, readonly string[]>>
/** Workspace packages whose complete runtime surface is safe across duplicate installations. */
const DUPLICATE_SAFE_PACKAGES: readonly string[] = [
'@deepseek-ai/dsh-brand',
'@deepseek-ai/dsh-typert-protocol',
'@deepseek-ai/dsh-util-crypto',
'@deepseek-ai/dsh-util-values',
]
/**
* Runtime exports whose values remain valid when npm installs another package copy.
*/
const SAFE_HOST_DEPENDENCY_EXPORTS = {
'@deepseek-ai/dsh-api-session-controller/remote-events': ['SESSION_CONTROLLER_REMOTE_EVENTS'],
'@deepseek-ai/dsh-credentials': ['credentialKey'],
'@deepseek-ai/dsh-deque': ['Deque'],
'@deepseek-ai/dsh-llm': ['MessageId', 'callConfigEquals', 'deepFreeze', 'freezeMessage'],
'@deepseek-ai/dsh-llm/brand': ['ToolCallId'],
'@deepseek-ai/dsh-session': ['isJsonValue'],
'@deepseek-ai/dsh-session/types': ['SessionId'],
'@deepseek-ai/dsh-settings': ['settingsNamespace'],
'@deepseek-ai/dsh-system-prompt': ['FIRST_PARTY_SECTION_ORDER'],
'@deepseek-ai/dsh-llm': ['callConfigEquals'],
'@deepseek-ai/dsh-timeout': ['MAX_TIMER_DELAY_MS'],
'@deepseek-ai/dsh-typert-protocol': ['RemoteError', 'remoteErrorOf'],
'@deepseek-ai/dsh-util-crypto': ['randomUUID'],
'@deepseek-ai/schemastery': ['default'],
} as const satisfies HostDependencyExports
/** Runtime exports that require every consumer to resolve the provider's shared peer instance. */
const PEER_REQUIRED_HOST_EXPORTS = {
'@deepseek-ai/dsh-scope': ['carrierKeyOf', 'scopeOf', 'scopeTarget'],
'@deepseek-ai/dsh-typert-protocol': ['Remote', 'TypertRemoteService', 'remoteMethods'],
} as const satisfies HostDependencyExports
/** Exact import specifier to reviewed runtime exports. */
@ -63,6 +62,7 @@ export interface PackageDependencyPolicy {
readonly clientFaceExclude: readonly string[]
readonly hostPackages: readonly string[]
readonly configurationOnlyDevDependencies: Readonly<Record<string, readonly string[]>>
readonly duplicateSafePackages?: readonly string[]
readonly safeHostDependencyExports: HostDependencyExports
readonly peerRequiredHostExports: HostDependencyExports
}
@ -73,6 +73,7 @@ export const PACKAGE_DEPENDENCY_POLICY: PackageDependencyPolicy = {
clientFaceExclude: CLIENT_FACE_EXCLUDE,
hostPackages: HOST_DEPENDENCY_PACKAGES,
configurationOnlyDevDependencies: CONFIGURATION_ONLY_DEV_DEPENDENCIES,
duplicateSafePackages: DUPLICATE_SAFE_PACKAGES,
safeHostDependencyExports: SAFE_HOST_DEPENDENCY_EXPORTS,
peerRequiredHostExports: PEER_REQUIRED_HOST_EXPORTS,
}

View file

@ -87,6 +87,39 @@ function facts(manifest: PackageDependencyManifest): PackageDependencyFacts {
}
}
function hostRuntimeFixture(): {
provider: WorkspacePackageManifest
workspaceNames: Set<string>
consumerFacts: PackageDependencyFacts
} {
const consumer = pkg('@f/consumer', 'packages/core/consumer/package.json')
const provider = pkg('@f/provider', 'packages/core/provider/package.json')
const sourcePath = 'packages/core/consumer/src/index.ts'
const specifier = `${provider.name}/api`
const workspaceNames = new Set([CORDIS, consumer.name, provider.name])
const consumerFacts: PackageDependencyFacts = {
manifestPath: consumer.manifestPath,
role: 'configured-host',
manifest: consumer.manifest,
workspaceNames,
allSourceUses: new Map(),
hostRuntimeSourceUses: new Map([[provider.name, [sourcePath]]]),
hostRuntimeExportUses: [{
packageName: provider.name,
specifier,
exportName: 'safeValue',
sourcePath,
line: 1,
column: 10,
sourceLine: `import { safeValue } from '${specifier}'`,
}],
peerRequiredHostDependencies: new Set(),
configurationOnlyDevDependencies: new Set(),
clientInject: new Set(),
}
return { provider, workspaceNames, consumerFacts }
}
describe('package dependency scope', () => {
it('keeps the measured Host relay roster explicit', () => {
expect(PACKAGE_DEPENDENCY_POLICY.clientFaceExclude).toEqual([
@ -109,20 +142,20 @@ describe('package dependency scope', () => {
'@deepseek-ai/dsh-client-ui-theme': ['@deepseek-ai/dsh-api-remotes'],
'@deepseek-ai/dsh-client-ui-tool': ['@deepseek-ai/dsh-api-remotes'],
})
expect(PACKAGE_DEPENDENCY_POLICY.duplicateSafePackages).toEqual([
'@deepseek-ai/dsh-brand',
'@deepseek-ai/dsh-typert-protocol',
'@deepseek-ai/dsh-util-crypto',
'@deepseek-ai/dsh-util-values',
])
expect(PACKAGE_DEPENDENCY_POLICY.safeHostDependencyExports['@deepseek-ai/dsh-deque']).toEqual(['Deque'])
expect(PACKAGE_DEPENDENCY_POLICY.safeHostDependencyExports['@deepseek-ai/schemastery']).toEqual(['default'])
expect(PACKAGE_DEPENDENCY_POLICY.safeHostDependencyExports['@deepseek-ai/dsh-session/types']).toEqual([
'SessionId',
])
expect(PACKAGE_DEPENDENCY_POLICY.safeHostDependencyExports['@deepseek-ai/dsh-typert-protocol']).toEqual([
'RemoteError', 'remoteErrorOf',
])
expect(PACKAGE_DEPENDENCY_POLICY.safeHostDependencyExports['@deepseek-ai/dsh-session/types']).toBeUndefined()
expect(PACKAGE_DEPENDENCY_POLICY.safeHostDependencyExports['@deepseek-ai/dsh-typert-protocol']).toBeUndefined()
expect(PACKAGE_DEPENDENCY_POLICY.peerRequiredHostExports['@deepseek-ai/dsh-scope']).toEqual([
'carrierKeyOf', 'scopeOf', 'scopeTarget',
])
expect(PACKAGE_DEPENDENCY_POLICY.peerRequiredHostExports['@deepseek-ai/dsh-typert-protocol']).toEqual([
'Remote', 'TypertRemoteService', 'remoteMethods',
])
expect(PACKAGE_DEPENDENCY_POLICY.peerRequiredHostExports['@deepseek-ai/dsh-typert-protocol']).toBeUndefined()
})
it('discovers the Client directory, dsh.client declarations, and configured Host packages', () => {
@ -175,29 +208,7 @@ describe('package dependency scope', () => {
})
it('rejects stale, duplicate, and unbounded safe Host export entries', () => {
const consumer = pkg('@f/consumer', 'packages/core/consumer/package.json')
const provider = pkg('@f/provider', 'packages/core/provider/package.json')
const workspaceNames = new Set([CORDIS, consumer.name, provider.name])
const consumerFacts: PackageDependencyFacts = {
manifestPath: consumer.manifestPath,
role: 'configured-host',
manifest: consumer.manifest,
workspaceNames,
allSourceUses: new Map(),
hostRuntimeSourceUses: new Map([[provider.name, ['packages/core/consumer/src/index.ts']]]),
hostRuntimeExportUses: [{
packageName: provider.name,
specifier: `${provider.name}/api`,
exportName: 'safeValue',
sourcePath: 'packages/core/consumer/src/index.ts',
line: 1,
column: 10,
sourceLine: "import { safeValue } from '@f/provider/api'",
}],
peerRequiredHostDependencies: new Set(),
configurationOnlyDevDependencies: new Set(),
clientInject: new Set(),
}
const { provider, workspaceNames, consumerFacts } = hostRuntimeFixture()
expect(collectHostDependencyExportPolicyViolations(
[consumerFacts],
@ -217,6 +228,29 @@ describe('package dependency scope', () => {
expect.stringContaining('appears in both Host export classifications'),
]))
})
it('applies a duplicate-safe package classification to its subpaths', () => {
const { provider, workspaceNames, consumerFacts } = hostRuntimeFixture()
expect(collectHostDependencyExportPolicyViolations(
[consumerFacts],
workspaceNames,
{
duplicateSafePackages: [provider.name],
safeHostDependencyExports: {},
peerRequiredHostExports: {},
},
)).toEqual([])
expect(collectHostDependencyExportPolicyViolations(
[consumerFacts],
workspaceNames,
{
duplicateSafePackages: [provider.name],
safeHostDependencyExports: { [`${provider.name}/api`]: ['safeValue'] },
peerRequiredHostExports: {},
},
)).toContain(`safeHostDependencyExports redundantly classifies duplicate-install-safe package ${provider.name}/api`)
})
})
describe('face-aware source classification', () => {

View file

@ -374,10 +374,19 @@ export function readPackageDependencyFacts(
export function collectHostDependencyExportPolicyViolations(
facts: readonly PackageDependencyFacts[],
workspaceNames: ReadonlySet<string>,
policy: Pick<PackageDependencyPolicy, 'peerRequiredHostExports' | 'safeHostDependencyExports'>,
policy: Pick<PackageDependencyPolicy, 'duplicateSafePackages' | 'peerRequiredHostExports' | 'safeHostDependencyExports'>,
): string[] {
const violations: string[] = []
const allRuntimeUses = facts.flatMap(fact => fact.hostRuntimeExportUses)
const duplicateSafePackages = new Set(policy.duplicateSafePackages ?? [])
for (const packageName of duplicates(policy.duplicateSafePackages ?? [])) {
violations.push(`duplicateSafePackages lists ${packageName} more than once`)
}
for (const packageName of duplicateSafePackages) {
if (!workspaceNames.has(packageName)) {
violations.push(`duplicateSafePackages names unknown workspace package ${packageName}`)
}
}
const classifications = [
['safeHostDependencyExports', policy.safeHostDependencyExports],
['peerRequiredHostExports', policy.peerRequiredHostExports],
@ -387,6 +396,8 @@ export function collectHostDependencyExportPolicyViolations(
const provider = packageNameOf(specifier)
if (provider === undefined || !workspaceNames.has(provider)) {
violations.push(`${field} specifier ${specifier} is not a workspace package`)
} else if (duplicateSafePackages.has(provider)) {
violations.push(`${field} redundantly classifies duplicate-install-safe package ${specifier}`)
}
if (exportNames.length === 0) {
violations.push(`${field} lists no exports for ${specifier}`)
@ -414,6 +425,7 @@ export function collectHostDependencyExportPolicyViolations(
for (const use of fact.hostRuntimeExportUses) {
if (use.packageName === fact.manifest.name || use.packageName === CORDIS) continue
if (!workspaceNames.has(use.packageName)) continue
if (duplicateSafePackages.has(use.packageName)) continue
if (policy.safeHostDependencyExports[use.specifier]?.includes(use.exportName) === true) continue
if (policy.peerRequiredHostExports[use.specifier]?.includes(use.exportName) === true) continue
violations.push(

View file

@ -16,7 +16,7 @@ const CANONICAL = '## Known Limitations and Deferred Work'
/** Packages audited as having no limitations section, keyed by repo-relative directory. */
const NO_LIMITATIONS: Readonly<Record<string, string>> = {
'packages/util/brand': 'Type-only nominal-branding primitive with no runtime behavior or deferred work.',
'packages/util/brand': 'Stateless nominal-string and canonical-key helpers have no deferred work.',
}
/** A heading that reads as a limitations section — canonical or drifted. */

View file

@ -31,10 +31,11 @@ interface SentenceContract {
*/
const NO_MODEL_EXPERIENCE_SECTION: Readonly<Record<string, string>> = {
'packages/core/scope': 'The package is a model-agnostic registration and lifecycle primitive; model-facing consumers own any context selection.',
'packages/util/brand': 'The package is a type-only primitive erased at compile time.',
'packages/util/brand': 'The package only constructs plain string values and registers nothing model-facing.',
'packages/util/home-paths': 'The package only resolves harness-owned host paths; model-facing consumers own any rendered use.',
'packages/util/launch-environment': 'The package only resolves host environment values; model-facing consumers own any rendered use.',
'packages/util/workspace-path': 'The package only formats Workspace paths for browser UI; it never constructs model input.',
'packages/util/values': 'The package only validates, snapshots, compares, freezes, or rejects caller-owned values; consumers own every model-facing use.',
}
/**