Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/testing/main-capability-coverage.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"version": 1,
"audit": {
"base": "2b34c653119bdf480f2af0330ee3809b51441807",
"head": "86a597dbe65633fd3328534788890febeb1644fd",
"head": "4b3d7e25531865cbdd203830e71c964a57207de8",
"ignoredDocumentationCommits": [
"dd86a325e3db5aa013ffa18a647e67e9fa279c10",
"0b0e12b4d97ec7d77a8a93076459fdaad2e52070",
Expand Down Expand Up @@ -476,7 +476,8 @@
"4b2ceebe6e7514c388fc64cfce1bfb9ffd37404a",
"156080d8a49e51ec8f86d77538c428cde7831bd4",
"4cf025e589a2f7426ae1f68d40c774429b6b9855",
"8aeef7b953132be14677977d21d41ffe1e051dd0"
"8aeef7b953132be14677977d21d41ffe1e051dd0",
"6cc7fca077b4929411d6dbaede79e8e560346ece"
]
},
"capabilities": [
Expand Down Expand Up @@ -2352,7 +2353,8 @@
"3b26e9c4214ed9784eefe80a228003bcd1d185d1",
"b7d6c83aecc814bf517321a9c65667792e5ca835",
"630595db25f3f3723c85aa863de001982a1f6aaa",
"86a597dbe65633fd3328534788890febeb1644fd"
"86a597dbe65633fd3328534788890febeb1644fd",
"4b3d7e25531865cbdd203830e71c964a57207de8"
],
"behaviors": [
"ARCH-POLICY-SCHEMA-001",
Expand Down
61 changes: 48 additions & 13 deletions scripts/architecture/trustedKernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
// One canonical glob implementation (charter: one canonical truth) — the
// kernel and the legacy harness must classify with identical semantics.
const { compileGlob } = require('./loadArchitecturePolicy');

const ROOT = path.resolve(__dirname, '..', '..');

Expand All @@ -35,11 +38,6 @@ function readJson(filePath, errors) {
}
}

function compileGlob(pattern) {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*\*/g, '<<<GLOBSTAR>>>').replace(/\*/g, '[^/]*').replace(/<<<GLOBSTAR>>>/g, '.*');
return new RegExp(`^${escaped}$`);
}

// ── policy loading (from the given root directory) ───────────────────

function loadPolicy(rootDir, errors) {
Expand All @@ -56,6 +54,7 @@ function loadPolicy(rootDir, errors) {
roles: (mod.roles || []).map(role => ({
role: role.role,
include: (role.include || []).map(compileGlob),
rawInclude: (role.include || []).slice(),
})),
}));

Expand Down Expand Up @@ -89,11 +88,30 @@ function loadPolicy(rootDir, errors) {

// ── file discovery ───────────────────────────────────────────────────

// The head tree is materialized into a plain directory (no .git), so
// discovery walks the filesystem instead of shelling out to git.
function discoverFiles(rootDir) {
const out = execFileSync('git', ['ls-files', '--cached', '--', 'src/'], {
cwd: rootDir, encoding: 'utf8', maxBuffer: 10 * 1024 * 1024,
}).trim();
return out.split('\n').filter(line => line.endsWith('.ts') && !line.endsWith('.d.ts'));
const files = [];
const walk = dir => {
let entries;
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
} catch {
return;
}
for (const entry of entries) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
walk(full);
continue;
}
if (!entry.isFile()) { continue; }
const rel = path.relative(rootDir, full).split(path.sep).join('/');
if (rel.endsWith('.ts') && !rel.endsWith('.d.ts')) { files.push(rel); }
}
};
walk(path.join(rootDir, 'src'));
return files.sort();
}

// ── closed-world classification ──────────────────────────────────────
Expand All @@ -114,16 +132,33 @@ function classifyFiles(files, modules, errors) {
continue;
}
const mod = owners[0];
const roleMatches = mod.roles.filter(r => r.include.some(pat => pat.test(file)));
if (roleMatches.length === 0) {
errors.push(`closed-world: ${file} (module ${mod.id}) has no matching role`);
// Remainder-role rule (same as the canonical policy loader): a role
// whose include is exactly ["**"] must be last and matches only
// module files no earlier role claimed. Overlap between
// non-remainder roles is an error, never silently first-match.
const remainderIndex = mod.roles.findIndex(candidate => {
const raw = candidate.rawInclude || [];
return raw.length === 1 && raw[0] === '**';
});
if (remainderIndex !== -1 && remainderIndex !== mod.roles.length - 1) {
errors.push(`policy: ${mod.id} remainder role '**' must be the last role`);
continue;
}
const specific = remainderIndex === -1 ? mod.roles : mod.roles.slice(0, remainderIndex);
const roleMatches = specific.filter(r => r.include.some(pat => pat.test(file)));
if (roleMatches.length > 1) {
errors.push(`closed-world: ${file} (module ${mod.id}) matches multiple roles: ${roleMatches.map(r => r.role).join(', ')}`);
continue;
}
classified.push({ file, module: mod.id, role: roleMatches[0].role });
if (roleMatches.length === 1) {
classified.push({ file, module: mod.id, role: roleMatches[0].role });
continue;
}
if (remainderIndex !== -1) {
classified.push({ file, module: mod.id, role: mod.roles[remainderIndex].role });
continue;
}
errors.push(`closed-world: ${file} (module ${mod.id}) has no matching role`);
}
return classified;
}
Expand Down
47 changes: 47 additions & 0 deletions tests/unit/architecture/trustedKernel.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,53 @@ test('TRUSTED-KERNEL-001 controlled mutation: a file in two roles fails', () =>
fs.rmSync(root, { recursive: true, force: true });
});

// ── remainder-role rule (canonical parity) ────────────────────────────

test('TRUSTED-KERNEL-001 the "**" remainder role claims only files no earlier role claimed', () => {
const root = tmpDir();
writeTree(root, {
'src/alpha/index.ts': 'export const x = 1;',
'src/alpha/internal/helper.ts': 'export const y = 2;',
'docs/testing/architecture-modules.json': JSON.stringify({ version: 1, scope: { roots: ['src'] }, modules: [
{ id: 'MOD-ALPHA', source: { include: ['src/**'], exclude: [] }, publicEntrypoints: ['src/alpha/index.ts'], mayDependOn: [], productCapabilities: ['CAP-1'], roles: [
{ role: 'composition', include: ['src/alpha/index.ts'] },
{ role: 'application', include: ['**'] },
]},
]}),
'docs/testing/main-capability-coverage.json': JSON.stringify({ version: 1, capabilities: [{ id: 'CAP-1' }] }),
});
const { modules, errors } = loadPolicy(root, []);
if (errors.length > 0) { assert.fail('policy load should succeed: ' + errors.join(', ')); }
const classErrors = [];
const classified = classifyFiles(['src/alpha/index.ts', 'src/alpha/internal/helper.ts'], modules, classErrors);
assert.deepEqual(classErrors, []);
assert.equal(classified.find(c => c.file === 'src/alpha/index.ts').role, 'composition',
'a specific earlier role wins over the remainder');
assert.equal(classified.find(c => c.file === 'src/alpha/internal/helper.ts').role, 'application',
'the remainder claims what no earlier role claimed');
fs.rmSync(root, { recursive: true, force: true });
});

test('TRUSTED-KERNEL-001 controlled mutation: a remainder role that is not last fails', () => {
const root = tmpDir();
writeTree(root, {
'src/alpha/index.ts': 'export const x = 1;',
'docs/testing/architecture-modules.json': JSON.stringify({ version: 1, scope: { roots: ['src'] }, modules: [
{ id: 'MOD-ALPHA', source: { include: ['src/**'], exclude: [] }, publicEntrypoints: ['src/alpha/index.ts'], mayDependOn: [], productCapabilities: ['CAP-1'], roles: [
{ role: 'application', include: ['**'] },
{ role: 'composition', include: ['src/alpha/index.ts'] },
]},
]}),
'docs/testing/main-capability-coverage.json': JSON.stringify({ version: 1, capabilities: [{ id: 'CAP-1' }] }),
});
const { modules, errors } = loadPolicy(root, []);
if (errors.length > 0) { assert.fail('policy load should succeed: ' + errors.join(', ')); }
const classErrors = [];
classifyFiles(['src/alpha/index.ts'], modules, classErrors);
assert.ok(classErrors.some(e => e.includes('remainder role')), JSON.stringify(classErrors));
fs.rmSync(root, { recursive: true, force: true });
});

// ── mutation 4: illegal cross-module import ──────────────────────────

test('TRUSTED-KERNEL-001 controlled mutation: an undeclared cross-module edge fails', () => {
Expand Down
Loading