test(deps): enforce runtime dependency ownership
This commit is contained in:
parent
9135a13a8b
commit
34bdc81b47
8 changed files with 100 additions and 46 deletions
|
|
@ -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/)
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue