Register KB pack skills from operator-subset _skills (P2).

Load hasSkills packs onto preset/public planes with groupKb toggles; unify content has* keys and keep netx-ops pure.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-13 11:46:31 +08:00
parent fcdd11ac59
commit 2caa3c98d7
26 changed files with 1122 additions and 106 deletions

View file

@ -20,7 +20,7 @@ function validManifest(overrides = {}) {
packageType: 'operator-subset',
operator: { name: 'IOH', country: 'ID' },
version: '2026.09.01',
content: { regions: true, theory: false, packet: true },
content: { hasRegions: true, hasTheory: false, hasPacket: true },
...overrides,
}, null, 2)
}
@ -50,10 +50,12 @@ test('direct MANIFEST at kbRoot', () => {
assert.equal(snap.operatorName, 'IOH')
assert.equal(snap.country, 'ID')
assert.equal(snap.version, '2026.09.01')
assert.equal(snap.content.regions, true)
assert.equal(snap.content.theory, false)
assert.equal(snap.content.packet, true)
assert.equal(snap.content.skills, false)
assert.equal(snap.content.hasRegions, true)
assert.equal(snap.content.hasTheory, false)
assert.equal(snap.content.hasPacket, true)
assert.equal(snap.content.hasSkills, false)
assert.equal(snap.content.hasCommon, false)
assert.equal('regions' in snap.content, false)
})
})
@ -118,11 +120,28 @@ test('wrong schemaVersion / packageType rejected', () => {
)
})
test('legacy short content keys map to has*; no dual truth', () => {
const parsed = parseManifest(validManifest({ content: { regions: true, packet: true } }))
assert.equal(parsed.content.hasRegions, true)
assert.equal(parsed.content.hasPacket, true)
assert.equal(parsed.content.hasTheory, false)
assert.equal('regions' in parsed.content, false)
assert.equal('packet' in parsed.content, false)
})
test('contract has* wins over legacy short key', () => {
const parsed = parseManifest(validManifest({
content: { hasRegions: false, regions: true },
}))
assert.equal(parsed.content.hasRegions, false)
assert.equal('regions' in parsed.content, false)
})
test('content missing keys default false; missing content object fails', () => {
const parsed = parseManifest(validManifest({ content: { regions: true } }))
assert.equal(parsed.content.regions, true)
assert.equal(parsed.content.theory, false)
assert.equal(parsed.content.packet, false)
const parsed = parseManifest(validManifest({ content: { hasRegions: true } }))
assert.equal(parsed.content.hasRegions, true)
assert.equal(parsed.content.hasTheory, false)
assert.equal(parsed.content.hasPacket, false)
const withoutContent = JSON.stringify({
schemaVersion: '1.0',
packageType: 'operator-subset',
@ -170,5 +189,6 @@ test('contract content flags hasRegions etc. are accepted', () => {
assert.equal(snap.status, 'configured')
assert.equal(snap.content.hasRegions, true)
assert.equal(snap.content.hasSkills, true)
assert.equal(snap.content.hasCommon, true)
})
})

View file

@ -0,0 +1,95 @@
/**
* KB pack skill loader from realRoot/_skills.
*/
import assert from 'node:assert/strict'
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import test from 'node:test'
import { kbPackSkillsEnabled, registerKbPackSkills } from '../src/netx/kb-pack-skills.ts'
async function withTemp(run) {
const root = mkdtempSync(join(tmpdir(), 'netxops-kb-pack-'))
try {
await run(root)
} finally {
rmSync(root, { recursive: true, force: true })
}
}
function configuredSnap(realRoot, content = {}) {
return {
status: 'configured',
realRoot,
operatorName: 'IOH',
country: 'ID',
version: '1',
content: {
hasRegions: false,
hasTheory: false,
hasPacket: false,
hasCommon: false,
hasSkills: true,
...content,
},
errorMessage: '',
}
}
test('kbPackSkillsEnabled requires configured + hasSkills', () => {
assert.equal(kbPackSkillsEnabled(configuredSnap('/x')), true)
assert.equal(kbPackSkillsEnabled(configuredSnap('/x', { hasSkills: false })), false)
assert.equal(kbPackSkillsEnabled({
...configuredSnap('/x'),
status: 'unconfigured',
}), false)
})
test('registerKbPackSkills loads _skills/*/SKILL.md', async () => {
await withTemp(async (root) => {
const skillDir = join(root, '_skills', 'kb-troubleshoot')
mkdirSync(skillDir, { recursive: true })
writeFileSync(join(skillDir, 'SKILL.md'), `---
name: kb-troubleshoot
description: KB triage playbook
---
# kb-troubleshoot
`, 'utf8')
const registered = []
const ctx = {
logger: { info() {}, warn() {} },
skills: {
register(skill) {
registered.push(skill)
return () => {}
},
},
}
const dispose = await registerKbPackSkills(ctx, configuredSnap(root), { enabled: true })
assert.equal(registered.length, 1)
assert.equal(registered[0].name, 'kb-troubleshoot')
assert.equal(registered[0].provider, 'netxops-kb-pack')
dispose()
})
})
test('registerKbPackSkills no-op when disabled or hasSkills false', async () => {
const registered = []
const ctx = {
logger: { info() {}, warn() {} },
skills: {
register(skill) {
registered.push(skill)
return () => {}
},
},
}
await registerKbPackSkills(ctx, configuredSnap('/nope'), { enabled: false })
await registerKbPackSkills(ctx, configuredSnap('/nope', { hasSkills: false }), { enabled: true })
assert.equal(registered.length, 0)
})

View file

@ -25,7 +25,13 @@ test('publishKbContext updates store', () => {
operatorName: 'IOH',
country: 'ID',
version: '1',
content: { regions: true, theory: false, packet: false, skills: false },
content: {
hasRegions: true,
hasTheory: false,
hasPacket: false,
hasCommon: false,
hasSkills: false,
},
errorMessage: '',
})
const snap = getKbContext()
@ -34,6 +40,7 @@ test('publishKbContext updates store', () => {
applyKbEnv(snap)
assert.equal(process.env.KB_OPERATOR, 'IOH')
assert.equal(process.env.KB_COUNTRY, 'ID')
assert.ok(process.env.KB_CONTENT?.includes('"regions":true'))
assert.ok(process.env.KB_CONTENT?.includes('"hasRegions":true'))
assert.equal(process.env.KB_CONTENT?.includes('"regions"'), false)
resetKbContext()
})