@principles/core 1.213.0 → 1.215.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime-v2/__tests__/architecture-regression.test.js +74 -23
- package/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
- package/dist/runtime-v2/activation/writers/__tests__/rule-host-writer.test.js +13 -5
- package/dist/runtime-v2/activation/writers/__tests__/rule-host-writer.test.js.map +1 -1
- package/dist/runtime-v2/activation/writers/rule-host-writer.d.ts.map +1 -1
- package/dist/runtime-v2/activation/writers/rule-host-writer.js +15 -1
- package/dist/runtime-v2/activation/writers/rule-host-writer.js.map +1 -1
- package/dist/runtime-v2/proven-channel-baseline.js +1 -1
- package/dist/runtime-v2/proven-channel-baseline.js.map +1 -1
- package/package.json +1 -1
|
@@ -2072,12 +2072,16 @@ describe('PRI-146: RuleHostWriter shadow activation boundary', () => {
|
|
|
2072
2072
|
expect(src).not.toContain('eval(');
|
|
2073
2073
|
expect(src).not.toContain('new Function');
|
|
2074
2074
|
});
|
|
2075
|
-
it('
|
|
2075
|
+
it('SHADOW_AFTER_APPROVAL: rule-host-writer.ts uses shadow action for owner-approved activations (PRI-489)', async () => {
|
|
2076
2076
|
const { readFileSync } = await import('node:fs');
|
|
2077
2077
|
const { resolve } = await import('node:path');
|
|
2078
2078
|
const src = readFileSync(resolve(__dirname, '..', 'activation', 'writers', 'rule-host-writer.ts'), 'utf-8');
|
|
2079
|
-
|
|
2080
|
-
|
|
2079
|
+
// PRI-489: Owner approval creates a SHADOW activation first. The only
|
|
2080
|
+
// shadow -> live transition is `pd activation promote` (atomic action
|
|
2081
|
+
// rewrite in SqliteActivationStateStore.promoteActivation). Live action
|
|
2082
|
+
// must never appear as a return value from activate(), only in comments
|
|
2083
|
+
// documenting the promote path.
|
|
2084
|
+
expect(src).toContain("action: 'code_tool_hook_shadow_activate'");
|
|
2081
2085
|
expect(src).toContain('accepted_shadow');
|
|
2082
2086
|
});
|
|
2083
2087
|
it('USES_GATE: rule-host-writer.ts imports evaluateRefinerRuleHostGate', async () => {
|
|
@@ -3236,29 +3240,49 @@ describe('PRI-450 / PRI-462: core I/O seam registry guard', () => {
|
|
|
3236
3240
|
const ALLOWED_IO_FILES = new Set(isValidRegistry(registry)
|
|
3237
3241
|
? registry.seams.flatMap((s) => s.files)
|
|
3238
3242
|
: []);
|
|
3239
|
-
// Extract all import module paths from source code.
|
|
3243
|
+
// Extract all RUNTIME import module paths from source code.
|
|
3240
3244
|
// Handles: import ... from 'mod', import 'mod' (side-effect), and multiline imports.
|
|
3241
|
-
//
|
|
3245
|
+
// EXCLUDES `import type ... from` — type-only imports are erased at compile time
|
|
3246
|
+
// and do not introduce runtime I/O. Reuses the same pattern proven in the
|
|
3247
|
+
// PRI-215 extractImportModulePaths helper.
|
|
3248
|
+
//
|
|
3249
|
+
// The negative lookahead `(?!type\b)` after `import\s+` prevents matching
|
|
3250
|
+
// `import type { X } from 'mod'` and `import type X from 'mod'`.
|
|
3242
3251
|
function extractImportPaths(src) {
|
|
3243
3252
|
const paths = [];
|
|
3244
|
-
|
|
3253
|
+
// `import ... from 'mod'` — but NOT `import type ... from 'mod'`.
|
|
3254
|
+
for (const m of src.matchAll(/import\s+(?!type\b)[\s\S]*?from\s+['"]([^'"]+)['"]/g)) {
|
|
3245
3255
|
if (m[1] != null)
|
|
3246
3256
|
paths.push(m[1]);
|
|
3247
3257
|
}
|
|
3248
|
-
|
|
3258
|
+
// Side-effect `import 'mod'` — `import type 'mod'` is not valid TS so no
|
|
3259
|
+
// exclusion needed, but the lookahead keeps it defensive.
|
|
3260
|
+
for (const m of src.matchAll(/import\s+(?!type\b)['"]([^'"]+)['"]/g)) {
|
|
3249
3261
|
if (m[1] != null && !paths.includes(m[1]))
|
|
3250
3262
|
paths.push(m[1]);
|
|
3251
3263
|
}
|
|
3252
3264
|
return paths;
|
|
3253
3265
|
}
|
|
3254
|
-
// Check if any import path references
|
|
3255
|
-
|
|
3266
|
+
// Check if any runtime import path references a Node I/O module.
|
|
3267
|
+
// Covers fs, path, child_process, better-sqlite3 (including sub-paths like fs/promises).
|
|
3268
|
+
// Type-only imports (`import type ... from 'fs'`) are excluded by extractImportPaths.
|
|
3269
|
+
//
|
|
3270
|
+
// This MUST stay in sync with eslint.config.js `no-restricted-imports` patterns.
|
|
3271
|
+
// ESLint uses `allowTypeImports: true` for child_process/better-sqlite3, so the
|
|
3272
|
+
// arch test is the runtime-I/O backstop for those modules.
|
|
3273
|
+
function importsNodeIo(src) {
|
|
3256
3274
|
const paths = extractImportPaths(src);
|
|
3257
3275
|
return paths.some((p) => p === 'fs' || p.startsWith('fs/') ||
|
|
3258
3276
|
p === 'node:fs' || p.startsWith('node:fs/') ||
|
|
3259
3277
|
p === 'path' || p.startsWith('path/') ||
|
|
3260
|
-
p === 'node:path' || p.startsWith('node:path/')
|
|
3278
|
+
p === 'node:path' || p.startsWith('node:path/') ||
|
|
3279
|
+
p === 'child_process' || p.startsWith('child_process/') ||
|
|
3280
|
+
p === 'node:child_process' || p.startsWith('node:child_process/') ||
|
|
3281
|
+
p === 'better-sqlite3');
|
|
3261
3282
|
}
|
|
3283
|
+
// Back-compat alias: keep the old name working for any external callers/tests
|
|
3284
|
+
// that referenced importsFsOrPath directly. New code should use importsNodeIo.
|
|
3285
|
+
const importsFsOrPath = importsNodeIo;
|
|
3262
3286
|
function collectTsFiles(dir, acc) {
|
|
3263
3287
|
const entries = fsSync.readdirSync(dir, { withFileTypes: true });
|
|
3264
3288
|
for (const entry of entries) {
|
|
@@ -3319,14 +3343,14 @@ describe('PRI-450 / PRI-462: core I/O seam registry guard', () => {
|
|
|
3319
3343
|
expect(weak).toEqual([]);
|
|
3320
3344
|
});
|
|
3321
3345
|
// ── Registry ↔ production files (PRI-450, EP-02) ──────────────────────────
|
|
3322
|
-
it('core/src/ production files: only registry-listed files import fs/path', () => {
|
|
3346
|
+
it('core/src/ production files: only registry-listed files import Node I/O modules (fs/path/child_process/better-sqlite3)', () => {
|
|
3323
3347
|
const coreSrcDir = pathSync.resolve(__dirname, '..', '..');
|
|
3324
3348
|
const allFiles = [];
|
|
3325
3349
|
collectTsFiles(coreSrcDir, allFiles);
|
|
3326
3350
|
const violations = [];
|
|
3327
3351
|
for (const filePath of allFiles) {
|
|
3328
3352
|
const content = fsSync.readFileSync(filePath, 'utf-8');
|
|
3329
|
-
if (
|
|
3353
|
+
if (importsNodeIo(content)) {
|
|
3330
3354
|
const relPath = pathSync.relative(coreSrcDir, filePath).replace(/\\/g, '/');
|
|
3331
3355
|
if (!ALLOWED_IO_FILES.has(relPath)) {
|
|
3332
3356
|
violations.push(relPath);
|
|
@@ -3334,8 +3358,9 @@ describe('PRI-450 / PRI-462: core I/O seam registry guard', () => {
|
|
|
3334
3358
|
}
|
|
3335
3359
|
}
|
|
3336
3360
|
if (violations.length > 0) {
|
|
3337
|
-
throw new Error(`Found ${violations.length} file(s) importing fs/path that are NOT in io-seam-registry.json.\n` +
|
|
3361
|
+
throw new Error(`Found ${violations.length} file(s) importing fs/path/child_process/better-sqlite3 that are NOT in io-seam-registry.json.\n` +
|
|
3338
3362
|
`Add them to the appropriate seam in packages/principles-core/io-seam-registry.json.\n` +
|
|
3363
|
+
`Note: \`import type\` from these modules is allowed (erased at runtime) and won't trigger this.\n` +
|
|
3339
3364
|
`Violations:\n ` + violations.join('\n '));
|
|
3340
3365
|
}
|
|
3341
3366
|
});
|
|
@@ -3363,19 +3388,45 @@ describe('PRI-450 / PRI-462: core I/O seam registry guard', () => {
|
|
|
3363
3388
|
expect(eslintSrc).toContain('io-seam-registry.json');
|
|
3364
3389
|
});
|
|
3365
3390
|
// ── Helper unit tests ─────────────────────────────────────────────────────
|
|
3366
|
-
it('
|
|
3391
|
+
it('importsNodeIo detects sub-path imports (fs/promises) and side-effect imports', () => {
|
|
3367
3392
|
// Sub-path import
|
|
3368
|
-
expect(
|
|
3369
|
-
expect(
|
|
3393
|
+
expect(importsNodeIo(`import { mkdir } from 'node:fs/promises';`)).toBe(true);
|
|
3394
|
+
expect(importsNodeIo(`import { posix } from 'path/posix';`)).toBe(true);
|
|
3370
3395
|
// Side-effect import
|
|
3371
|
-
expect(
|
|
3372
|
-
expect(
|
|
3396
|
+
expect(importsNodeIo(`import 'fs';`)).toBe(true);
|
|
3397
|
+
expect(importsNodeIo(`import "node:path";`)).toBe(true);
|
|
3373
3398
|
// Standard import
|
|
3374
|
-
expect(
|
|
3375
|
-
expect(
|
|
3376
|
-
// Non-
|
|
3377
|
-
expect(
|
|
3378
|
-
expect(
|
|
3399
|
+
expect(importsNodeIo(`import * as fs from 'fs';`)).toBe(true);
|
|
3400
|
+
expect(importsNodeIo(`import { resolve } from 'node:path';`)).toBe(true);
|
|
3401
|
+
// Non-I/O imports
|
|
3402
|
+
expect(importsNodeIo(`import { foo } from 'bar';`)).toBe(false);
|
|
3403
|
+
expect(importsNodeIo(``)).toBe(false);
|
|
3404
|
+
});
|
|
3405
|
+
it('importsNodeIo detects child_process and better-sqlite3 runtime imports', () => {
|
|
3406
|
+
// Runtime child_process imports
|
|
3407
|
+
expect(importsNodeIo(`import { spawn } from 'child_process';`)).toBe(true);
|
|
3408
|
+
expect(importsNodeIo(`import { execSync } from 'node:child_process';`)).toBe(true);
|
|
3409
|
+
expect(importsNodeIo(`import { fork } from 'child_process/promises';`)).toBe(true);
|
|
3410
|
+
// Runtime better-sqlite3 import
|
|
3411
|
+
expect(importsNodeIo(`import Database from 'better-sqlite3';`)).toBe(true);
|
|
3412
|
+
// Mixed runtime + type import from child_process (still runtime I/O)
|
|
3413
|
+
expect(importsNodeIo(`import { spawn, type SpawnOptions } from 'child_process';`)).toBe(true);
|
|
3414
|
+
});
|
|
3415
|
+
it('importsNodeIo EXCLUDES `import type` (erased at runtime, no I/O)', () => {
|
|
3416
|
+
// Type-only imports from forbidden modules — must NOT trigger
|
|
3417
|
+
expect(importsNodeIo(`import type { Database } from 'better-sqlite3';`)).toBe(false);
|
|
3418
|
+
expect(importsNodeIo(`import type BetterSqlite3 from 'better-sqlite3';`)).toBe(false);
|
|
3419
|
+
expect(importsNodeIo(`import type { SpawnOptions } from 'child_process';`)).toBe(false);
|
|
3420
|
+
expect(importsNodeIo(`import type { Stats } from 'node:fs';`)).toBe(false);
|
|
3421
|
+
expect(importsNodeIo(`import type { Platform } from 'node:process';`)).toBe(false);
|
|
3422
|
+
// Mixed: a real `import type` next to a runtime import from a DIFFERENT module
|
|
3423
|
+
// — the type import is excluded, the runtime import is detected.
|
|
3424
|
+
expect(importsNodeIo(`import type { Database } from 'better-sqlite3';\nimport { spawn } from 'child_process';`)).toBe(true);
|
|
3425
|
+
});
|
|
3426
|
+
it('importsFsOrPath alias stays in sync with importsNodeIo', () => {
|
|
3427
|
+
// The back-compat alias must point to the same implementation.
|
|
3428
|
+
expect(importsFsOrPath(`import { spawn } from 'child_process';`)).toBe(true);
|
|
3429
|
+
expect(importsFsOrPath(`import type { Database } from 'better-sqlite3';`)).toBe(false);
|
|
3379
3430
|
});
|
|
3380
3431
|
});
|
|
3381
3432
|
//# sourceMappingURL=architecture-regression.test.js.map
|