@jskit-ai/kernel 0.1.4

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.
Files changed (185) hide show
  1. package/README.md +24 -0
  2. package/_testable/index.js +4 -0
  3. package/client/appConfig.js +33 -0
  4. package/client/componentInteraction.js +51 -0
  5. package/client/componentInteraction.test.js +111 -0
  6. package/client/descriptorSections.js +75 -0
  7. package/client/index.d.ts +70 -0
  8. package/client/index.js +3 -0
  9. package/client/logging.js +38 -0
  10. package/client/moduleBootstrap.js +670 -0
  11. package/client/moduleBootstrap.test.js +403 -0
  12. package/client/shellBootstrap.js +233 -0
  13. package/client/shellBootstrap.test.js +185 -0
  14. package/client/shellRouting.js +321 -0
  15. package/client/shellRouting.test.js +113 -0
  16. package/client/vite/clientBootstrapPlugin.js +259 -0
  17. package/client/vite/clientBootstrapPlugin.test.js +563 -0
  18. package/client/vite/index.js +3 -0
  19. package/internal/node/fileSystem.js +21 -0
  20. package/internal/node/installedPackageDescriptor.js +104 -0
  21. package/package.json +43 -0
  22. package/server/actions/ActionRuntimeServiceProvider.js +309 -0
  23. package/server/actions/ActionRuntimeServiceProvider.test.js +551 -0
  24. package/server/actions/index.js +8 -0
  25. package/server/container/ContainerCoreServiceProvider.js +27 -0
  26. package/server/container/index.js +10 -0
  27. package/server/exportPolicy.test.js +68 -0
  28. package/server/http/HttpFastifyServiceProvider.js +25 -0
  29. package/server/http/_testable/index.js +2 -0
  30. package/server/http/index.js +1 -0
  31. package/server/http/lib/controller.js +183 -0
  32. package/server/http/lib/controller.test.js +143 -0
  33. package/server/http/lib/errors.js +12 -0
  34. package/server/http/lib/httpRuntime.js +82 -0
  35. package/server/http/lib/index.js +18 -0
  36. package/server/http/lib/kernel.js +15 -0
  37. package/server/http/lib/kernel.test.js +880 -0
  38. package/server/http/lib/middlewareRuntime.js +149 -0
  39. package/server/http/lib/requestActionExecutor.js +258 -0
  40. package/server/http/lib/requestScope.js +59 -0
  41. package/server/http/lib/routeRegistration.js +165 -0
  42. package/server/http/lib/routeSupport.js +45 -0
  43. package/server/http/lib/routeValidator.js +469 -0
  44. package/server/http/lib/routeValidator.test.js +474 -0
  45. package/server/http/lib/router.js +206 -0
  46. package/server/kernel/KernelCoreServiceProvider.js +27 -0
  47. package/server/kernel/index.js +10 -0
  48. package/server/platform/PlatformServerRuntimeServiceProvider.js +30 -0
  49. package/server/platform/index.js +5 -0
  50. package/server/platform/providerRuntime/descriptorCatalog.js +170 -0
  51. package/server/platform/providerRuntime/helpers.js +45 -0
  52. package/server/platform/providerRuntime/lockfile.js +27 -0
  53. package/server/platform/providerRuntime/providerLoader.js +283 -0
  54. package/server/platform/providerRuntime.js +142 -0
  55. package/server/platform/providerRuntime.test.js +217 -0
  56. package/server/platform/runtime.js +40 -0
  57. package/server/platform/surfaceRuntime.js +150 -0
  58. package/server/platform/surfaceRuntime.test.js +136 -0
  59. package/server/registries/actionSurfaceSourceRegistry.js +150 -0
  60. package/server/registries/bootstrapPayloadContributorRegistry.js +41 -0
  61. package/server/registries/domainEventListenerRegistry.js +61 -0
  62. package/server/registries/index.js +36 -0
  63. package/server/registries/primitives.js +63 -0
  64. package/server/registries/routeVisibilityResolverRegistry.js +87 -0
  65. package/server/registries/serviceRegistrationRegistry.js +431 -0
  66. package/server/runtime/ServerRuntimeCoreServiceProvider.js +65 -0
  67. package/server/runtime/ServerRuntimeCoreServiceProvider.test.js +53 -0
  68. package/server/runtime/apiRoutePolicyParity.test.js +109 -0
  69. package/server/runtime/apiRouteRegistration.js +65 -0
  70. package/server/runtime/bootBootstrapRoutes.js +46 -0
  71. package/server/runtime/bootBootstrapRoutes.test.js +79 -0
  72. package/server/runtime/bootstrapContributors.test.js +114 -0
  73. package/server/runtime/canonicalJson.js +74 -0
  74. package/server/runtime/composition.js +142 -0
  75. package/server/runtime/domainEvents.test.js +114 -0
  76. package/server/runtime/domainRules.js +50 -0
  77. package/server/runtime/domainRules.test.js +87 -0
  78. package/server/runtime/entityChangeEvents.js +182 -0
  79. package/server/runtime/entityChangeEvents.test.js +211 -0
  80. package/server/runtime/errors.js +68 -0
  81. package/server/runtime/errors.test.js +73 -0
  82. package/server/runtime/fastifyBootstrap.js +372 -0
  83. package/server/runtime/fastifyBootstrap.test.js +194 -0
  84. package/server/runtime/index.js +6 -0
  85. package/server/runtime/integers.js +13 -0
  86. package/server/runtime/moduleConfig.js +269 -0
  87. package/server/runtime/moduleConfig.test.js +141 -0
  88. package/server/runtime/pagination.js +13 -0
  89. package/server/runtime/realtimeNormalization.js +21 -0
  90. package/server/runtime/requestUrl.js +38 -0
  91. package/server/runtime/routeUtils.js +20 -0
  92. package/server/runtime/runtimeAssembly.js +113 -0
  93. package/server/runtime/runtimeKernel.js +55 -0
  94. package/server/runtime/securityAudit.js +269 -0
  95. package/server/runtime/securityAudit.test.js +41 -0
  96. package/server/runtime/serviceAuthorization.js +113 -0
  97. package/server/runtime/serviceAuthorization.test.js +100 -0
  98. package/server/runtime/serviceRegistration.test.js +197 -0
  99. package/server/support/SupportCoreServiceProvider.js +25 -0
  100. package/server/support/appConfig.js +37 -0
  101. package/server/support/appConfig.test.js +94 -0
  102. package/server/support/defaultMissingHandler.js +7 -0
  103. package/server/support/index.js +2 -0
  104. package/server/support/routePolicyConfig.js +51 -0
  105. package/server/support/symlinkSafeRequire.js +78 -0
  106. package/server/support/symlinkSafeRequire.test.js +27 -0
  107. package/server/surface/SurfaceRoutingServiceProvider.js +27 -0
  108. package/server/surface/index.js +19 -0
  109. package/shared/actions/actionContributorHelpers.js +34 -0
  110. package/shared/actions/actionContributorHelpers.test.js +16 -0
  111. package/shared/actions/actionDefinitions.js +488 -0
  112. package/shared/actions/actionDefinitions.test.js +212 -0
  113. package/shared/actions/audit.js +7 -0
  114. package/shared/actions/executionContext.js +97 -0
  115. package/shared/actions/executionContext.test.js +66 -0
  116. package/shared/actions/idempotency.js +62 -0
  117. package/shared/actions/index.js +2 -0
  118. package/shared/actions/observability.js +10 -0
  119. package/shared/actions/pipeline.js +287 -0
  120. package/shared/actions/policies.js +342 -0
  121. package/shared/actions/policies.test.js +233 -0
  122. package/shared/actions/registry.js +187 -0
  123. package/shared/actions/registry.test.js +381 -0
  124. package/shared/actions/requestMeta.js +36 -0
  125. package/shared/actions/textNormalization.js +3 -0
  126. package/shared/actions/withActionDefaults.js +34 -0
  127. package/shared/index.js +2 -0
  128. package/shared/runtime/application.js +323 -0
  129. package/shared/runtime/container.js +261 -0
  130. package/shared/runtime/containerErrors.js +22 -0
  131. package/shared/runtime/index.js +18 -0
  132. package/shared/runtime/kernelErrors.js +20 -0
  133. package/shared/runtime/serviceProvider.js +13 -0
  134. package/shared/support/formatDateTime.js +10 -0
  135. package/shared/support/formatDateTime.test.js +15 -0
  136. package/shared/support/index.js +14 -0
  137. package/shared/support/linkPath.js +67 -0
  138. package/shared/support/linkPath.test.js +35 -0
  139. package/shared/support/normalize.js +116 -0
  140. package/shared/support/normalize.test.js +48 -0
  141. package/shared/support/packageDescriptor.test.js +121 -0
  142. package/shared/support/permissions.js +50 -0
  143. package/shared/support/pickOwnProperties.js +17 -0
  144. package/shared/support/pickOwnProperties.test.js +25 -0
  145. package/shared/support/policies.js +11 -0
  146. package/shared/support/queryPath.js +33 -0
  147. package/shared/support/queryPath.test.js +19 -0
  148. package/shared/support/queryResilience.js +34 -0
  149. package/shared/support/queryResilience.test.js +33 -0
  150. package/shared/support/returnToPath.js +153 -0
  151. package/shared/support/returnToPath.test.js +123 -0
  152. package/shared/support/sorting.js +15 -0
  153. package/shared/support/tokens.js +23 -0
  154. package/shared/support/tokens.test.js +17 -0
  155. package/shared/support/visibility.js +56 -0
  156. package/shared/support/visibility.test.js +45 -0
  157. package/shared/surface/apiPaths.js +84 -0
  158. package/shared/surface/escapeRegExp.js +5 -0
  159. package/shared/surface/index.js +6 -0
  160. package/shared/surface/paths.js +273 -0
  161. package/shared/surface/registry.js +135 -0
  162. package/shared/surface/registry.test.js +44 -0
  163. package/shared/surface/runtime.js +357 -0
  164. package/shared/surface/runtime.test.js +319 -0
  165. package/shared/validators/createCursorListValidator.js +42 -0
  166. package/shared/validators/createCursorListValidator.test.js +34 -0
  167. package/shared/validators/cursorPaginationQueryValidator.js +31 -0
  168. package/shared/validators/cursorPaginationQueryValidator.test.js +21 -0
  169. package/shared/validators/index.js +12 -0
  170. package/shared/validators/inputNormalization.js +13 -0
  171. package/shared/validators/mergeObjectSchemas.js +31 -0
  172. package/shared/validators/mergeObjectSchemas.test.js +67 -0
  173. package/shared/validators/mergeValidators.js +89 -0
  174. package/shared/validators/mergeValidators.test.js +116 -0
  175. package/shared/validators/nestValidator.js +53 -0
  176. package/shared/validators/nestValidator.test.js +60 -0
  177. package/shared/validators/recordIdParamsValidator.js +36 -0
  178. package/shared/validators/recordIdParamsValidator.test.js +20 -0
  179. package/shared/validators/resourceRequiredMetadata.js +41 -0
  180. package/shared/validators/resourceRequiredMetadata.test.js +49 -0
  181. package/test/barrelExposure.test.js +106 -0
  182. package/test/dynamicImportPolicy.test.js +89 -0
  183. package/test/exportsContract.test.js +168 -0
  184. package/test/routeInputContractGuard.test.js +78 -0
  185. package/test/surfaceIndependence.test.js +109 -0
@@ -0,0 +1,49 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { Type } from "typebox";
4
+ import {
5
+ normalizeRequiredFieldList,
6
+ deriveRequiredFieldsFromSchema,
7
+ deriveResourceRequiredMetadata
8
+ } from "./resourceRequiredMetadata.js";
9
+
10
+ test("normalizeRequiredFieldList trims, dedupes, and drops empty entries", () => {
11
+ assert.deepEqual(
12
+ normalizeRequiredFieldList([" name ", "color", "name", "", null]),
13
+ ["name", "color"]
14
+ );
15
+ assert.deepEqual(normalizeRequiredFieldList(undefined), []);
16
+ });
17
+
18
+ test("deriveRequiredFieldsFromSchema reads schema.required", () => {
19
+ const schema = Type.Object({
20
+ name: Type.String(),
21
+ color: Type.String(),
22
+ optionalField: Type.Optional(Type.String())
23
+ });
24
+
25
+ assert.deepEqual(deriveRequiredFieldsFromSchema(schema), ["name", "color"]);
26
+ assert.deepEqual(deriveRequiredFieldsFromSchema(Type.Partial(schema)), []);
27
+ });
28
+
29
+ test("deriveResourceRequiredMetadata reads create/replace/patch operation body schemas", () => {
30
+ const fullSchema = Type.Object({
31
+ name: Type.String(),
32
+ color: Type.String(),
33
+ invitesEnabled: Type.Boolean()
34
+ });
35
+ const patchSchema = Type.Partial(fullSchema);
36
+ const resource = {
37
+ operations: {
38
+ create: { bodyValidator: { schema: fullSchema } },
39
+ replace: { bodyValidator: { schema: fullSchema } },
40
+ patch: { bodyValidator: { schema: patchSchema } }
41
+ }
42
+ };
43
+
44
+ assert.deepEqual(deriveResourceRequiredMetadata(resource), {
45
+ create: ["name", "color", "invitesEnabled"],
46
+ replace: ["name", "color", "invitesEnabled"],
47
+ patch: []
48
+ });
49
+ });
@@ -0,0 +1,106 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import test from "node:test";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
8
+ const REPO_ROOT = path.resolve(TEST_DIRECTORY, "..", "..", "..");
9
+
10
+ const BARREL_EXPECTATIONS = Object.freeze([
11
+ Object.freeze({
12
+ filePath: path.join(REPO_ROOT, "packages", "kernel", "server", "runtime", "index.js"),
13
+ expectedExports: Object.freeze([
14
+ "AppError",
15
+ "createValidationError",
16
+ "installServiceRegistrationApi",
17
+ "parsePositiveInteger",
18
+ "requireAuth",
19
+ "resolveServiceRegistrations",
20
+ "registerDomainEventListener",
21
+ "registerBootstrapPayloadContributor"
22
+ ])
23
+ }),
24
+ Object.freeze({
25
+ filePath: path.join(REPO_ROOT, "packages", "kernel", "shared", "support", "index.js"),
26
+ expectedExports: Object.freeze([
27
+ "appendQueryString",
28
+ "formatDateTime",
29
+ "hasPermission",
30
+ "isRecord",
31
+ "isTransientQueryError",
32
+ "normalizePermissionList",
33
+ "normalizeReturnToPath",
34
+ "pickOwnProperties",
35
+ "resolveAllowedOriginsFromPlacementContext",
36
+ "shouldRetryTransientQueryFailure",
37
+ "transientQueryRetryDelay"
38
+ ])
39
+ }),
40
+ Object.freeze({
41
+ filePath: path.join(REPO_ROOT, "packages", "kernel", "shared", "actions", "index.js"),
42
+ expectedExports: Object.freeze([
43
+ "normalizeActionDefinition",
44
+ "withActionDefaults"
45
+ ])
46
+ }),
47
+ Object.freeze({
48
+ filePath: path.join(REPO_ROOT, "packages", "kernel", "client", "index.js"),
49
+ expectedExports: Object.freeze([
50
+ "bootstrapClientShellApp",
51
+ "createComponentInteractionEmitter",
52
+ "createShellRouter",
53
+ "getClientAppConfig",
54
+ "resolveClientBootstrapDebugEnabled"
55
+ ])
56
+ }),
57
+ Object.freeze({
58
+ filePath: path.join(REPO_ROOT, "packages", "kernel", "shared", "index.js"),
59
+ expectedExports: Object.freeze([
60
+ "normalizePathname",
61
+ "resolveLinkPath"
62
+ ])
63
+ }),
64
+ Object.freeze({
65
+ filePath: path.join(REPO_ROOT, "packages", "kernel", "server", "http", "index.js"),
66
+ expectedExports: Object.freeze([
67
+ "registerRouteVisibilityResolver"
68
+ ])
69
+ })
70
+ ]);
71
+
72
+ function collectBarrelNamedExports(filePath) {
73
+ const source = fs.readFileSync(filePath, "utf8");
74
+ const exportMatches = [...source.matchAll(/export\s*\{([\s\S]*?)\}\s*from\s*["'][^"']+["']/g)];
75
+ const names = [];
76
+
77
+ for (const match of exportMatches) {
78
+ const body = String(match[1] || "");
79
+ for (const item of body.split(",")) {
80
+ const entry = String(item || "").trim();
81
+ if (!entry) {
82
+ continue;
83
+ }
84
+
85
+ const alias = entry.split(/\sas\s/i).pop().trim();
86
+ if (!alias) {
87
+ continue;
88
+ }
89
+ names.push(alias);
90
+ }
91
+ }
92
+
93
+ return [...new Set(names)].sort((left, right) => left.localeCompare(right));
94
+ }
95
+
96
+ test("kernel barrel exports stay intentionally minimal", () => {
97
+ for (const expectation of BARREL_EXPECTATIONS) {
98
+ const observed = collectBarrelNamedExports(expectation.filePath);
99
+ const expected = [...expectation.expectedExports].sort((left, right) => left.localeCompare(right));
100
+ assert.deepEqual(
101
+ observed,
102
+ expected,
103
+ `Unexpected barrel exports in ${path.relative(REPO_ROOT, expectation.filePath)}`
104
+ );
105
+ }
106
+ });
@@ -0,0 +1,89 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import test from "node:test";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
8
+ const KERNEL_ROOT = path.resolve(TEST_DIRECTORY, "..");
9
+ const SCANNED_EXTENSIONS = new Set([".js", ".mjs", ".cjs", ".ts", ".tsx"]);
10
+ const IGNORED_DIRECTORIES = new Set(["node_modules", ".git", "dist", "coverage"]);
11
+ const NON_DETERMINISTIC_IMPORT_PATTERN = /(Date\.now\s*\(|Math\.random\s*\()/;
12
+
13
+ function normalizeSlash(value) {
14
+ return String(value || "").replace(/\\/g, "/");
15
+ }
16
+
17
+ function shouldScanFile(filePath) {
18
+ return SCANNED_EXTENSIONS.has(path.extname(filePath));
19
+ }
20
+
21
+ function walkFiles(directoryPath) {
22
+ const entries = fs.readdirSync(directoryPath, { withFileTypes: true });
23
+ const files = [];
24
+
25
+ for (const entry of entries) {
26
+ if (IGNORED_DIRECTORIES.has(entry.name)) {
27
+ continue;
28
+ }
29
+
30
+ const absolutePath = path.join(directoryPath, entry.name);
31
+ if (entry.isDirectory()) {
32
+ files.push(...walkFiles(absolutePath));
33
+ continue;
34
+ }
35
+
36
+ if (entry.isFile() && shouldScanFile(absolutePath)) {
37
+ files.push(absolutePath);
38
+ }
39
+ }
40
+
41
+ return files;
42
+ }
43
+
44
+ function resolveLineNumberFromIndex(source, index) {
45
+ if (index <= 0) {
46
+ return 1;
47
+ }
48
+ return source.slice(0, index).split(/\r?\n/).length;
49
+ }
50
+
51
+ function resolveImportStatementSlice(source, importStartIndex) {
52
+ const semicolonIndex = source.indexOf(";", importStartIndex);
53
+ if (semicolonIndex === -1) {
54
+ return source.slice(importStartIndex, importStartIndex + 400);
55
+ }
56
+ return source.slice(importStartIndex, semicolonIndex + 1);
57
+ }
58
+
59
+ function collectNonDeterministicDynamicImportViolations() {
60
+ const kernelFiles = walkFiles(KERNEL_ROOT);
61
+ const violations = [];
62
+
63
+ for (const filePath of kernelFiles) {
64
+ const source = fs.readFileSync(filePath, "utf8");
65
+ const relativePath = normalizeSlash(path.relative(KERNEL_ROOT, filePath));
66
+ const dynamicImportPattern = /\bimport\s*\(/g;
67
+ let match = null;
68
+
69
+ while ((match = dynamicImportPattern.exec(source))) {
70
+ const importStartIndex = match.index;
71
+ const importStatement = resolveImportStatementSlice(source, importStartIndex);
72
+ if (!NON_DETERMINISTIC_IMPORT_PATTERN.test(importStatement)) {
73
+ continue;
74
+ }
75
+ violations.push(`${relativePath}:${resolveLineNumberFromIndex(source, importStartIndex)}`);
76
+ }
77
+ }
78
+
79
+ return violations.sort();
80
+ }
81
+
82
+ test("kernel dynamic imports do not use Date.now or Math.random cache busting", () => {
83
+ const violations = collectNonDeterministicDynamicImportViolations();
84
+ assert.deepEqual(
85
+ violations,
86
+ [],
87
+ `Kernel dynamic imports must be deterministic. Remove Date.now/Math.random from: ${violations.join(", ")}`
88
+ );
89
+ });
@@ -0,0 +1,168 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import test from "node:test";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
8
+ const REPO_ROOT = path.resolve(TEST_DIRECTORY, "..", "..", "..");
9
+ const KERNEL_PACKAGE_JSON_PATH = path.join(
10
+ REPO_ROOT,
11
+ "packages",
12
+ "kernel",
13
+ "package.json"
14
+ );
15
+ const SCAN_ROOTS = [
16
+ path.join(REPO_ROOT, "packages"),
17
+ path.join(REPO_ROOT, "tooling", "create-app", "templates")
18
+ ];
19
+ const SCANNED_EXTENSIONS = new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".vue"]);
20
+ const IGNORED_DIRECTORIES = new Set(["node_modules", ".git", "dist", "coverage"]);
21
+ const EXCLUDED_USAGE_PATH_SEGMENTS = new Set(["test", "tests", "__tests__", "test-support"]);
22
+ const TEST_FILENAME_PATTERN = /\.(test|spec)\.[A-Za-z0-9]+$/;
23
+ const EXPORTED_UNUSED_ALLOWLIST = new Set(["./_testable"]);
24
+
25
+ function normalizeSlash(value) {
26
+ return String(value || "").replace(/\\/g, "/");
27
+ }
28
+
29
+ function shouldScanFile(filePath) {
30
+ return SCANNED_EXTENSIONS.has(path.extname(filePath));
31
+ }
32
+
33
+ function walkFiles(directoryPath) {
34
+ const entries = fs.readdirSync(directoryPath, { withFileTypes: true });
35
+ const files = [];
36
+
37
+ for (const entry of entries) {
38
+ if (IGNORED_DIRECTORIES.has(entry.name)) {
39
+ continue;
40
+ }
41
+
42
+ const absolutePath = path.join(directoryPath, entry.name);
43
+ if (entry.isDirectory()) {
44
+ files.push(...walkFiles(absolutePath));
45
+ continue;
46
+ }
47
+
48
+ if (entry.isFile() && shouldScanFile(absolutePath)) {
49
+ files.push(absolutePath);
50
+ }
51
+ }
52
+
53
+ return files;
54
+ }
55
+
56
+ function toExportKey(importSubpath) {
57
+ const normalizedSubpath = String(importSubpath || "").replace(/\/+$/g, "");
58
+ if (!normalizedSubpath) {
59
+ return ".";
60
+ }
61
+ return `./${normalizedSubpath}`;
62
+ }
63
+
64
+ function collectScanFiles() {
65
+ const files = [];
66
+ for (const rootPath of SCAN_ROOTS) {
67
+ if (!fs.existsSync(rootPath)) {
68
+ continue;
69
+ }
70
+ files.push(...walkFiles(rootPath));
71
+ }
72
+ return files;
73
+ }
74
+
75
+ function isProductionOrTemplateUsageFile(filePath) {
76
+ const relativePath = normalizeSlash(path.relative(REPO_ROOT, filePath));
77
+ if (relativePath.startsWith("packages/kernel/")) {
78
+ return false;
79
+ }
80
+
81
+ const relativeSegments = relativePath.split("/");
82
+ if (relativeSegments.some((segment) => EXCLUDED_USAGE_PATH_SEGMENTS.has(segment))) {
83
+ return false;
84
+ }
85
+
86
+ const baseName = path.posix.basename(relativePath);
87
+ if (TEST_FILENAME_PATTERN.test(baseName)) {
88
+ return false;
89
+ }
90
+
91
+ return true;
92
+ }
93
+
94
+ function collectKernelImportUsages() {
95
+ const candidateFiles = collectScanFiles().filter((filePath) => isProductionOrTemplateUsageFile(filePath));
96
+
97
+ const moduleSpecifierPatterns = [
98
+ /\bimport\s+(?:[^"'`]+?\s+from\s+)?["'`]([^"'`]+)["'`]/g,
99
+ /\bexport\s+[^"'`]+?\s+from\s+["'`]([^"'`]+)["'`]/g,
100
+ /\bimport\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g
101
+ ];
102
+ const usages = [];
103
+
104
+ for (const filePath of candidateFiles) {
105
+ const fileContent = fs.readFileSync(filePath, "utf8");
106
+ const relativePath = normalizeSlash(path.relative(REPO_ROOT, filePath));
107
+ for (const pattern of moduleSpecifierPatterns) {
108
+ let match = null;
109
+ while ((match = pattern.exec(fileContent))) {
110
+ const specifier = String(match[1] || "");
111
+ if (!specifier.startsWith("@jskit-ai/kernel")) {
112
+ continue;
113
+ }
114
+ const importSubpath = specifier.startsWith("@jskit-ai/kernel/")
115
+ ? specifier.slice("@jskit-ai/kernel/".length)
116
+ : "";
117
+ usages.push({
118
+ filePath: relativePath,
119
+ specifier,
120
+ exportKey: toExportKey(importSubpath)
121
+ });
122
+ }
123
+ }
124
+ }
125
+
126
+ return usages;
127
+ }
128
+
129
+ function describeMissingExports(usages, exportsMap) {
130
+ const missing = usages
131
+ .filter((usage) => !Object.prototype.hasOwnProperty.call(exportsMap, usage.exportKey))
132
+ .map((usage) => `${usage.exportKey} <- ${usage.filePath} (${usage.specifier})`)
133
+ .sort();
134
+
135
+ return missing;
136
+ }
137
+
138
+ test("kernel exports are explicit and aligned with repository usage", () => {
139
+ const packageJson = JSON.parse(fs.readFileSync(KERNEL_PACKAGE_JSON_PATH, "utf8"));
140
+ const exportsMap = packageJson.exports || {};
141
+ const exportKeys = Object.keys(exportsMap).sort();
142
+
143
+ const wildcardExports = exportKeys.filter((key) => key.includes("*"));
144
+ assert.deepEqual(
145
+ wildcardExports,
146
+ [],
147
+ `Kernel exports must be explicit. Remove wildcard keys: ${wildcardExports.join(", ")}`
148
+ );
149
+
150
+ const usages = collectKernelImportUsages();
151
+ const missingExports = describeMissingExports(usages, exportsMap);
152
+ assert.deepEqual(
153
+ missingExports,
154
+ [],
155
+ `Kernel imports missing from package exports:\n${missingExports.join("\n")}`
156
+ );
157
+
158
+ const usedExportKeys = new Set(usages.map((usage) => usage.exportKey));
159
+ const staleExports = exportKeys
160
+ .filter((key) => !usedExportKeys.has(key))
161
+ .filter((key) => !EXPORTED_UNUSED_ALLOWLIST.has(key));
162
+
163
+ assert.deepEqual(
164
+ staleExports,
165
+ [],
166
+ `Stale kernel exports found. Remove or allowlist with rationale: ${staleExports.join(", ")}`
167
+ );
168
+ });
@@ -0,0 +1,78 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { readdir, readFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
+ const REPO_ROOT = path.resolve(PACKAGE_ROOT, "..", "..");
9
+ const PACKAGES_ROOT = path.join(REPO_ROOT, "packages");
10
+ const SOURCE_SCOPE_MARKERS = ["/src/server/", "/templates/src/local-package/server/"];
11
+ const JS_EXTENSIONS = new Set([".js", ".mjs", ".cjs"]);
12
+ const DISALLOWED_PATTERNS = [
13
+ {
14
+ description: "spread pass-through from request.input",
15
+ regex: /\.{3}\s*request\.input\.(body|query|params)\b/g
16
+ },
17
+ {
18
+ description: "whole-section pass-through from request.input",
19
+ regex: /\binput\s*:\s*request\.input\.(body|query|params)\b/g
20
+ }
21
+ ];
22
+
23
+ async function listFilesRecursive(directoryPath) {
24
+ const entries = await readdir(directoryPath, { withFileTypes: true });
25
+ const files = [];
26
+
27
+ for (const entry of entries) {
28
+ const absolutePath = path.join(directoryPath, entry.name);
29
+ if (entry.isDirectory()) {
30
+ files.push(...(await listFilesRecursive(absolutePath)));
31
+ continue;
32
+ }
33
+ if (entry.isFile()) {
34
+ files.push(absolutePath);
35
+ }
36
+ }
37
+
38
+ return files;
39
+ }
40
+
41
+ function isRouteServerSourceFile(absolutePath) {
42
+ const normalizedPath = absolutePath.split(path.sep).join("/");
43
+ if (!SOURCE_SCOPE_MARKERS.some((marker) => normalizedPath.includes(marker))) {
44
+ return false;
45
+ }
46
+ return JS_EXTENSIONS.has(path.extname(absolutePath));
47
+ }
48
+
49
+ function findLineNumber(sourceText, index) {
50
+ return sourceText.slice(0, index).split("\n").length;
51
+ }
52
+
53
+ test("server route handlers do not pass through request.input sections", async () => {
54
+ const allFiles = await listFilesRecursive(PACKAGES_ROOT);
55
+ const targetFiles = allFiles.filter((absolutePath) => isRouteServerSourceFile(absolutePath));
56
+ const violations = [];
57
+
58
+ for (const absolutePath of targetFiles) {
59
+ const sourceText = await readFile(absolutePath, "utf8");
60
+ for (const { regex, description } of DISALLOWED_PATTERNS) {
61
+ regex.lastIndex = 0;
62
+ let match = regex.exec(sourceText);
63
+ while (match) {
64
+ const line = findLineNumber(sourceText, match.index);
65
+ violations.push(
66
+ `${path.relative(REPO_ROOT, absolutePath)}:${line} ${description}: ${match[0]}`
67
+ );
68
+ match = regex.exec(sourceText);
69
+ }
70
+ }
71
+ }
72
+
73
+ assert.deepEqual(
74
+ violations,
75
+ [],
76
+ `Found request.input pass-through patterns:\n${violations.join("\n")}`
77
+ );
78
+ });
@@ -0,0 +1,109 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import test from "node:test";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
8
+ const REPO_ROOT = path.resolve(TEST_DIRECTORY, "..", "..", "..");
9
+ const KERNEL_ROOT = path.join(REPO_ROOT, "packages", "kernel");
10
+ const IGNORED_DIRECTORIES = new Set(["node_modules", ".git", "dist", "coverage"]);
11
+ const DISALLOWED_SURFACE_LITERALS = new Set(["app", "admin", "console", "home"]);
12
+ const ALLOWED_PUBLIC_LITERAL_FILES = new Set([
13
+ "packages/kernel/shared/support/policies.js"
14
+ ]);
15
+
16
+ function walkFiles(directoryPath) {
17
+ const entries = fs.readdirSync(directoryPath, { withFileTypes: true });
18
+ const files = [];
19
+
20
+ for (const entry of entries) {
21
+ if (IGNORED_DIRECTORIES.has(entry.name)) {
22
+ continue;
23
+ }
24
+ const absolutePath = path.join(directoryPath, entry.name);
25
+ if (entry.isDirectory()) {
26
+ files.push(...walkFiles(absolutePath));
27
+ continue;
28
+ }
29
+ if (!entry.isFile()) {
30
+ continue;
31
+ }
32
+ if (!absolutePath.endsWith(".js") || absolutePath.endsWith(".test.js")) {
33
+ continue;
34
+ }
35
+ files.push(absolutePath);
36
+ }
37
+
38
+ return files;
39
+ }
40
+
41
+ function toRelativePath(filePath) {
42
+ return path.relative(REPO_ROOT, filePath).replace(/\\/g, "/");
43
+ }
44
+
45
+ function collectExactStringLiteralHits(source, values) {
46
+ const hits = [];
47
+ const pattern = /(["'])([^"'\\]*(?:\\.[^"'\\]*)*)\1/g;
48
+ let match = null;
49
+
50
+ while ((match = pattern.exec(source))) {
51
+ const literal = String(match[2] || "");
52
+ if (!values.has(literal)) {
53
+ continue;
54
+ }
55
+ hits.push({
56
+ literal,
57
+ index: match.index
58
+ });
59
+ }
60
+
61
+ return hits;
62
+ }
63
+
64
+ test("kernel non-test code stays surface-id agnostic", () => {
65
+ const files = walkFiles(KERNEL_ROOT);
66
+ const forbiddenHits = [];
67
+
68
+ for (const filePath of files) {
69
+ const source = fs.readFileSync(filePath, "utf8");
70
+ const relativePath = toRelativePath(filePath);
71
+ const literalHits = collectExactStringLiteralHits(source, DISALLOWED_SURFACE_LITERALS);
72
+
73
+ for (const hit of literalHits) {
74
+ forbiddenHits.push(`${relativePath}:${hit.index} -> "${hit.literal}"`);
75
+ }
76
+ }
77
+
78
+ assert.deepEqual(
79
+ forbiddenHits,
80
+ [],
81
+ `Kernel must not hardcode concrete surface ids:\n${forbiddenHits.join("\n")}`
82
+ );
83
+ });
84
+
85
+ test("kernel uses \"public\" literal only in policy files", () => {
86
+ const files = walkFiles(KERNEL_ROOT);
87
+ const disallowedPublicHits = [];
88
+
89
+ for (const filePath of files) {
90
+ const source = fs.readFileSync(filePath, "utf8");
91
+ const relativePath = toRelativePath(filePath);
92
+ const publicHits = collectExactStringLiteralHits(source, new Set(["public"]));
93
+ if (publicHits.length < 1) {
94
+ continue;
95
+ }
96
+ if (ALLOWED_PUBLIC_LITERAL_FILES.has(relativePath)) {
97
+ continue;
98
+ }
99
+ for (const hit of publicHits) {
100
+ disallowedPublicHits.push(`${relativePath}:${hit.index} -> "public"`);
101
+ }
102
+ }
103
+
104
+ assert.deepEqual(
105
+ disallowedPublicHits,
106
+ [],
107
+ `Kernel must not treat "public" as a hardcoded surface default:\n${disallowedPublicHits.join("\n")}`
108
+ );
109
+ });