mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-09 05:10:45 +08:00
Add operator-subset knowledge base wiring for Netx Ops P1.
Locate and validate MANIFEST under kbRoot, show anti-mixup status in settings, and inject KB_* env plus kb-context so sessions degrade to pure netx when unset. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
e6f9d062ed
commit
de7c6a0c4b
23 changed files with 2694 additions and 68 deletions
151
test/kb-manifest.test.mjs
Normal file
151
test/kb-manifest.test.mjs
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/**
|
||||
* MANIFEST locate + validate for operator-subset knowledge packages.
|
||||
*/
|
||||
|
||||
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 {
|
||||
findManifest,
|
||||
parseManifest,
|
||||
resolveKbRoot,
|
||||
} from '../src/netx/kb-manifest.ts'
|
||||
|
||||
function validManifest(overrides = {}) {
|
||||
return JSON.stringify({
|
||||
schemaVersion: '1.0',
|
||||
packageType: 'operator-subset',
|
||||
operator: { name: 'IOH', country: 'ID' },
|
||||
version: '2026.09.01',
|
||||
content: { regions: true, theory: false, packet: true },
|
||||
...overrides,
|
||||
}, null, 2)
|
||||
}
|
||||
|
||||
function withTemp(run) {
|
||||
const root = mkdtempSync(join(tmpdir(), 'netxops-kb-'))
|
||||
try {
|
||||
run(root)
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
test('empty kbRoot → unconfigured', () => {
|
||||
const snap = resolveKbRoot('')
|
||||
assert.equal(snap.status, 'unconfigured')
|
||||
assert.equal(snap.realRoot, '')
|
||||
assert.equal(snap.errorMessage, '')
|
||||
})
|
||||
|
||||
test('direct MANIFEST at kbRoot', () => {
|
||||
withTemp((root) => {
|
||||
writeFileSync(join(root, 'MANIFEST.json'), validManifest(), 'utf8')
|
||||
const snap = resolveKbRoot(root)
|
||||
assert.equal(snap.status, 'configured')
|
||||
assert.equal(snap.realRoot, root)
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
test('nested one level under maxDepth', () => {
|
||||
withTemp((root) => {
|
||||
const pkg = join(root, 'packages', 'ioh')
|
||||
mkdirSync(pkg, { recursive: true })
|
||||
writeFileSync(join(pkg, 'MANIFEST.json'), validManifest({
|
||||
operator: { name: 'XL', country: 'ID' },
|
||||
version: '1.2.3',
|
||||
}), 'utf8')
|
||||
const snap = resolveKbRoot(root)
|
||||
assert.equal(snap.status, 'configured')
|
||||
assert.equal(snap.realRoot, pkg)
|
||||
assert.equal(snap.operatorName, 'XL')
|
||||
assert.equal(snap.version, '1.2.3')
|
||||
})
|
||||
})
|
||||
|
||||
test('zero manifests → error', () => {
|
||||
withTemp((root) => {
|
||||
mkdirSync(join(root, 'empty'), { recursive: true })
|
||||
const snap = resolveKbRoot(root)
|
||||
assert.equal(snap.status, 'error')
|
||||
assert.match(snap.errorMessage, /no MANIFEST\.json/)
|
||||
})
|
||||
})
|
||||
|
||||
test('multiple nested manifests → error', () => {
|
||||
withTemp((root) => {
|
||||
const a = join(root, 'a')
|
||||
const b = join(root, 'b')
|
||||
mkdirSync(a, { recursive: true })
|
||||
mkdirSync(b, { recursive: true })
|
||||
writeFileSync(join(a, 'MANIFEST.json'), validManifest(), 'utf8')
|
||||
writeFileSync(join(b, 'MANIFEST.json'), validManifest({
|
||||
operator: { name: 'Other', country: 'XX' },
|
||||
}), 'utf8')
|
||||
const snap = resolveKbRoot(root)
|
||||
assert.equal(snap.status, 'error')
|
||||
assert.match(snap.errorMessage, /ambiguous/)
|
||||
})
|
||||
})
|
||||
|
||||
test('bad JSON → error', () => {
|
||||
withTemp((root) => {
|
||||
writeFileSync(join(root, 'MANIFEST.json'), '{not-json', 'utf8')
|
||||
const snap = resolveKbRoot(root)
|
||||
assert.equal(snap.status, 'error')
|
||||
assert.match(snap.errorMessage, /not valid JSON/)
|
||||
})
|
||||
})
|
||||
|
||||
test('wrong schemaVersion / packageType rejected', () => {
|
||||
assert.throws(
|
||||
() => parseManifest(validManifest({ schemaVersion: '2.0' })),
|
||||
/schemaVersion/,
|
||||
)
|
||||
assert.throws(
|
||||
() => parseManifest(validManifest({ packageType: 'full-tree' })),
|
||||
/packageType/,
|
||||
)
|
||||
})
|
||||
|
||||
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 withoutContent = JSON.stringify({
|
||||
schemaVersion: '1.0',
|
||||
packageType: 'operator-subset',
|
||||
operator: { name: 'IOH', country: 'ID' },
|
||||
version: '1',
|
||||
})
|
||||
assert.throws(() => parseManifest(withoutContent), /content/)
|
||||
})
|
||||
|
||||
test('findManifest respects maxDepth', () => {
|
||||
withTemp((root) => {
|
||||
const deep = join(root, 'a', 'b', 'c', 'd')
|
||||
mkdirSync(deep, { recursive: true })
|
||||
writeFileSync(join(deep, 'MANIFEST.json'), validManifest(), 'utf8')
|
||||
const tooDeep = findManifest(root, 3)
|
||||
assert.equal(tooDeep.paths.length, 0)
|
||||
const ok = findManifest(root, 4)
|
||||
assert.equal(ok.paths.length, 1)
|
||||
})
|
||||
})
|
||||
|
||||
test('missing kbRoot path → error', () => {
|
||||
const snap = resolveKbRoot(join(tmpdir(), 'netxops-kb-missing-' + Date.now()))
|
||||
assert.equal(snap.status, 'error')
|
||||
assert.match(snap.errorMessage, /not found/)
|
||||
})
|
||||
39
test/kb-runtime.test.mjs
Normal file
39
test/kb-runtime.test.mjs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* KB runtime env mirroring.
|
||||
*/
|
||||
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import { resolveKbRoot } from '../src/netx/kb-manifest.ts'
|
||||
import { applyKbEnv, getKbContext, publishKbContext, resetKbContext } from '../src/netx/kb-runtime.ts'
|
||||
|
||||
test('applyKbEnv clears identity when unconfigured', () => {
|
||||
process.env.KB_ROOT = '/tmp/x'
|
||||
process.env.KB_OPERATOR = 'IOH'
|
||||
applyKbEnv(resolveKbRoot(''))
|
||||
assert.equal(process.env.KB_STATUS, 'unconfigured')
|
||||
assert.equal(process.env.KB_ROOT, undefined)
|
||||
assert.equal(process.env.KB_OPERATOR, undefined)
|
||||
resetKbContext()
|
||||
})
|
||||
|
||||
test('publishKbContext updates store', () => {
|
||||
publishKbContext({
|
||||
status: 'configured',
|
||||
realRoot: '/kb',
|
||||
operatorName: 'IOH',
|
||||
country: 'ID',
|
||||
version: '1',
|
||||
content: { regions: true, theory: false, packet: false, skills: false },
|
||||
errorMessage: '',
|
||||
})
|
||||
const snap = getKbContext()
|
||||
assert.equal(snap.status, 'configured')
|
||||
assert.equal(snap.operatorName, 'IOH')
|
||||
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'))
|
||||
resetKbContext()
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue