@stsepelin/checktrail 0.1.0-alpha.1
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/CONTRIBUTING.md +42 -0
- package/LICENSE +21 -0
- package/README.md +355 -0
- package/SECURITY.md +46 -0
- package/dist/src/actionlint-evidence.d.ts +2 -0
- package/dist/src/actionlint-evidence.js +166 -0
- package/dist/src/actionlint-inputs.d.ts +5 -0
- package/dist/src/actionlint-inputs.js +123 -0
- package/dist/src/actionlint-runner.d.ts +1 -0
- package/dist/src/actionlint-runner.js +122 -0
- package/dist/src/actionlint.d.ts +19 -0
- package/dist/src/actionlint.js +80 -0
- package/dist/src/adapters.d.ts +49 -0
- package/dist/src/adapters.js +430 -0
- package/dist/src/architecture.d.ts +107 -0
- package/dist/src/architecture.js +230 -0
- package/dist/src/clang-evidence.d.ts +2 -0
- package/dist/src/clang-evidence.js +194 -0
- package/dist/src/clang-protocol.d.ts +3 -0
- package/dist/src/clang-protocol.js +25 -0
- package/dist/src/clang-runner.d.ts +1 -0
- package/dist/src/clang-runner.js +159 -0
- package/dist/src/clang.d.ts +44 -0
- package/dist/src/clang.js +197 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +338 -0
- package/dist/src/config.d.ts +33 -0
- package/dist/src/config.js +140 -0
- package/dist/src/contract-schema.d.ts +85 -0
- package/dist/src/contract-schema.js +56 -0
- package/dist/src/contract-worker.d.ts +1 -0
- package/dist/src/contract-worker.js +123 -0
- package/dist/src/contracts.d.ts +22 -0
- package/dist/src/contracts.js +123 -0
- package/dist/src/django-evidence.d.ts +2 -0
- package/dist/src/django-evidence.js +100 -0
- package/dist/src/django-runner.d.ts +1 -0
- package/dist/src/django-runner.js +80 -0
- package/dist/src/django.d.ts +9 -0
- package/dist/src/django.js +50 -0
- package/dist/src/dotnet-compiler.d.ts +1 -0
- package/dist/src/dotnet-compiler.js +87 -0
- package/dist/src/dotnet-evidence.d.ts +2 -0
- package/dist/src/dotnet-evidence.js +127 -0
- package/dist/src/dotnet-runner.d.ts +1 -0
- package/dist/src/dotnet-runner.js +167 -0
- package/dist/src/dotnet.d.ts +65 -0
- package/dist/src/dotnet.js +120 -0
- package/dist/src/engine.d.ts +19 -0
- package/dist/src/engine.js +239 -0
- package/dist/src/environment.d.ts +6 -0
- package/dist/src/environment.js +36 -0
- package/dist/src/eslint-evidence.d.ts +2 -0
- package/dist/src/eslint-evidence.js +98 -0
- package/dist/src/eslint-runner.d.ts +1 -0
- package/dist/src/eslint-runner.js +61 -0
- package/dist/src/eslint.d.ts +2 -0
- package/dist/src/eslint.js +40 -0
- package/dist/src/evidence.d.ts +2 -0
- package/dist/src/evidence.js +313 -0
- package/dist/src/external-adapter.d.ts +164 -0
- package/dist/src/external-adapter.js +265 -0
- package/dist/src/external-evidence.d.ts +2 -0
- package/dist/src/external-evidence.js +127 -0
- package/dist/src/external-runner.d.ts +1 -0
- package/dist/src/external-runner.js +122 -0
- package/dist/src/fastapi-evidence.d.ts +2 -0
- package/dist/src/fastapi-evidence.js +126 -0
- package/dist/src/fastapi-runner.d.ts +1 -0
- package/dist/src/fastapi-runner.js +84 -0
- package/dist/src/fastapi.d.ts +10 -0
- package/dist/src/fastapi.js +52 -0
- package/dist/src/fetch-pack.d.ts +16 -0
- package/dist/src/fetch-pack.js +154 -0
- package/dist/src/finding-policy-schema.d.ts +146 -0
- package/dist/src/finding-policy-schema.js +70 -0
- package/dist/src/finding-policy.d.ts +62 -0
- package/dist/src/finding-policy.js +206 -0
- package/dist/src/git-selection.d.ts +3 -0
- package/dist/src/git-selection.js +229 -0
- package/dist/src/go-directives.d.ts +1 -0
- package/dist/src/go-directives.js +32 -0
- package/dist/src/go-scope.d.ts +11 -0
- package/dist/src/go-scope.js +122 -0
- package/dist/src/golangci-evidence.d.ts +2 -0
- package/dist/src/golangci-evidence.js +92 -0
- package/dist/src/golangci-runner.d.ts +1 -0
- package/dist/src/golangci-runner.js +124 -0
- package/dist/src/golangci.d.ts +2 -0
- package/dist/src/golangci.js +35 -0
- package/dist/src/guidance.d.ts +98 -0
- package/dist/src/guidance.js +247 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.js +16 -0
- package/dist/src/inventory.d.ts +4 -0
- package/dist/src/inventory.js +94 -0
- package/dist/src/java-compiler.d.ts +1 -0
- package/dist/src/java-compiler.js +110 -0
- package/dist/src/java-evidence.d.ts +2 -0
- package/dist/src/java-evidence.js +114 -0
- package/dist/src/java-runner.d.ts +1 -0
- package/dist/src/java-runner.js +79 -0
- package/dist/src/java.d.ts +25 -0
- package/dist/src/java.js +102 -0
- package/dist/src/jest-evidence.d.ts +2 -0
- package/dist/src/jest-evidence.js +117 -0
- package/dist/src/jest-runner.d.ts +1 -0
- package/dist/src/jest-runner.js +46 -0
- package/dist/src/jest.d.ts +2 -0
- package/dist/src/jest.js +36 -0
- package/dist/src/junit.d.ts +18 -0
- package/dist/src/junit.js +157 -0
- package/dist/src/laravel-evidence.d.ts +2 -0
- package/dist/src/laravel-evidence.js +175 -0
- package/dist/src/laravel-runner.d.ts +1 -0
- package/dist/src/laravel-runner.js +168 -0
- package/dist/src/laravel.d.ts +8 -0
- package/dist/src/laravel.js +50 -0
- package/dist/src/local-tool.d.ts +1 -0
- package/dist/src/local-tool.js +19 -0
- package/dist/src/mcp.d.ts +14 -0
- package/dist/src/mcp.js +362 -0
- package/dist/src/mutation.d.ts +118 -0
- package/dist/src/mutation.js +370 -0
- package/dist/src/mypy-evidence.d.ts +2 -0
- package/dist/src/mypy-evidence.js +63 -0
- package/dist/src/mypy.d.ts +2 -0
- package/dist/src/mypy.js +59 -0
- package/dist/src/node-reporter.d.ts +1 -0
- package/dist/src/node-reporter.js +28 -0
- package/dist/src/nuxt-evidence.d.ts +2 -0
- package/dist/src/nuxt-evidence.js +160 -0
- package/dist/src/nuxt-protocol.d.ts +52 -0
- package/dist/src/nuxt-protocol.js +29 -0
- package/dist/src/nuxt-runner.d.ts +1 -0
- package/dist/src/nuxt-runner.js +165 -0
- package/dist/src/nuxt.d.ts +24 -0
- package/dist/src/nuxt.js +105 -0
- package/dist/src/output.d.ts +3 -0
- package/dist/src/output.js +48 -0
- package/dist/src/phpstan-evidence.d.ts +2 -0
- package/dist/src/phpstan-evidence.js +81 -0
- package/dist/src/phpstan.d.ts +2 -0
- package/dist/src/phpstan.js +51 -0
- package/dist/src/phpunit-evidence.d.ts +2 -0
- package/dist/src/phpunit-evidence.js +53 -0
- package/dist/src/phpunit.d.ts +2 -0
- package/dist/src/phpunit.js +58 -0
- package/dist/src/pint-evidence.d.ts +2 -0
- package/dist/src/pint-evidence.js +79 -0
- package/dist/src/pint-runner.d.ts +1 -0
- package/dist/src/pint-runner.js +53 -0
- package/dist/src/pint.d.ts +2 -0
- package/dist/src/pint.js +37 -0
- package/dist/src/playwright-evidence.d.ts +2 -0
- package/dist/src/playwright-evidence.js +127 -0
- package/dist/src/playwright-reporter.d.ts +10 -0
- package/dist/src/playwright-reporter.js +37 -0
- package/dist/src/playwright-runner.d.ts +1 -0
- package/dist/src/playwright-runner.js +61 -0
- package/dist/src/playwright.d.ts +2 -0
- package/dist/src/playwright.js +35 -0
- package/dist/src/policy-pack.d.ts +21 -0
- package/dist/src/policy-pack.js +41 -0
- package/dist/src/pytest-evidence.d.ts +2 -0
- package/dist/src/pytest-evidence.js +116 -0
- package/dist/src/pytest-runner.d.ts +1 -0
- package/dist/src/pytest-runner.js +48 -0
- package/dist/src/pytest.d.ts +2 -0
- package/dist/src/pytest.js +25 -0
- package/dist/src/report-validation.d.ts +129 -0
- package/dist/src/report-validation.js +27 -0
- package/dist/src/review.d.ts +270 -0
- package/dist/src/review.js +349 -0
- package/dist/src/ruby.d.ts +2 -0
- package/dist/src/ruby.js +25 -0
- package/dist/src/ruff-evidence.d.ts +2 -0
- package/dist/src/ruff-evidence.js +99 -0
- package/dist/src/ruff.d.ts +2 -0
- package/dist/src/ruff.js +40 -0
- package/dist/src/runner.d.ts +8 -0
- package/dist/src/runner.js +117 -0
- package/dist/src/runtime-inventory.d.ts +112 -0
- package/dist/src/runtime-inventory.js +200 -0
- package/dist/src/rust-dep-info.d.ts +1 -0
- package/dist/src/rust-dep-info.js +46 -0
- package/dist/src/rust-evidence.d.ts +2 -0
- package/dist/src/rust-evidence.js +157 -0
- package/dist/src/rust-runner.d.ts +1 -0
- package/dist/src/rust-runner.js +161 -0
- package/dist/src/rust.d.ts +7 -0
- package/dist/src/rust.js +39 -0
- package/dist/src/sarif.d.ts +86 -0
- package/dist/src/sarif.js +113 -0
- package/dist/src/schemas.d.ts +393 -0
- package/dist/src/schemas.js +222 -0
- package/dist/src/staticcheck-evidence.d.ts +2 -0
- package/dist/src/staticcheck-evidence.js +75 -0
- package/dist/src/swift.d.ts +2 -0
- package/dist/src/swift.js +20 -0
- package/dist/src/task-store.d.ts +37 -0
- package/dist/src/task-store.js +349 -0
- package/dist/src/tool-versions.d.ts +3 -0
- package/dist/src/tool-versions.js +260 -0
- package/dist/src/types.d.ts +157 -0
- package/dist/src/types.js +37 -0
- package/dist/src/typescript-build-evidence.d.ts +2 -0
- package/dist/src/typescript-build-evidence.js +86 -0
- package/dist/src/typescript-build-runner.d.ts +1 -0
- package/dist/src/typescript-build-runner.js +132 -0
- package/dist/src/typescript-build.d.ts +2 -0
- package/dist/src/typescript-build.js +41 -0
- package/dist/src/typescript.d.ts +2 -0
- package/dist/src/typescript.js +54 -0
- package/dist/src/validation-task-worker.d.ts +1 -0
- package/dist/src/validation-task-worker.js +205 -0
- package/dist/src/validation-tasks-protocol.d.ts +69 -0
- package/dist/src/validation-tasks-protocol.js +57 -0
- package/dist/src/validation-tasks.d.ts +11 -0
- package/dist/src/validation-tasks.js +144 -0
- package/dist/src/vitest-evidence.d.ts +2 -0
- package/dist/src/vitest-evidence.js +112 -0
- package/dist/src/vitest-runner.d.ts +1 -0
- package/dist/src/vitest-runner.js +48 -0
- package/dist/src/vitest.d.ts +2 -0
- package/dist/src/vitest.js +36 -0
- package/dist/src/vue-router-capture.d.ts +42 -0
- package/dist/src/vue-router-capture.js +87 -0
- package/dist/src/vue-router-evidence.d.ts +2 -0
- package/dist/src/vue-router-evidence.js +126 -0
- package/dist/src/vue-router-protocol.d.ts +63 -0
- package/dist/src/vue-router-protocol.js +44 -0
- package/dist/src/vue-router-runner.d.ts +1 -0
- package/dist/src/vue-router-runner.js +88 -0
- package/dist/src/vue-router.d.ts +23 -0
- package/dist/src/vue-router.js +108 -0
- package/dist/src/vue-tsc-runner.d.ts +1 -0
- package/dist/src/vue-tsc-runner.js +24 -0
- package/dist/src/workspace.d.ts +7 -0
- package/dist/src/workspace.js +53 -0
- package/docs/ACCEPTANCE.md +56 -0
- package/docs/ACTIONLINT.md +112 -0
- package/docs/ARCHITECTURE-POLICY.md +82 -0
- package/docs/ARCHITECTURE.md +122 -0
- package/docs/CLANG.md +113 -0
- package/docs/CLIENTS.md +109 -0
- package/docs/CONTRACTS.md +73 -0
- package/docs/DEPENDENCIES.md +42 -0
- package/docs/DJANGO.md +70 -0
- package/docs/DOTNET.md +114 -0
- package/docs/ENVIRONMENTS.md +52 -0
- package/docs/ESLINT.md +68 -0
- package/docs/EVALUATION.md +124 -0
- package/docs/EXECUTION.md +250 -0
- package/docs/EXTERNAL-ADAPTERS.md +170 -0
- package/docs/EXTERNAL-EVALUATION.md +155 -0
- package/docs/EXTERNAL-RUFF-EVALUATION.md +85 -0
- package/docs/FASTAPI.md +73 -0
- package/docs/FINDING-POLICY.md +67 -0
- package/docs/GO-RACE.md +19 -0
- package/docs/GO-SCOPE.md +63 -0
- package/docs/GOLANGCI-LINT.md +64 -0
- package/docs/GUIDANCE.md +46 -0
- package/docs/IMPACT-MEASUREMENT.md +97 -0
- package/docs/INSTALLATION.md +102 -0
- package/docs/JAVA.md +79 -0
- package/docs/JEST.md +36 -0
- package/docs/JUNIT.md +31 -0
- package/docs/LANGUAGES.md +113 -0
- package/docs/LARAVEL.md +111 -0
- package/docs/MCP-COMPATIBILITY.md +68 -0
- package/docs/MUTATIONS.md +93 -0
- package/docs/MYPY.md +37 -0
- package/docs/NATIVE-CI.md +96 -0
- package/docs/NUXT.md +123 -0
- package/docs/PACK-DISTRIBUTION.md +98 -0
- package/docs/PERFORMANCE.md +71 -0
- package/docs/PEST.md +36 -0
- package/docs/PHPSTAN.md +48 -0
- package/docs/PHPUNIT.md +32 -0
- package/docs/PINT.md +41 -0
- package/docs/PLAN.md +265 -0
- package/docs/PLAYWRIGHT.md +73 -0
- package/docs/POLICY-PACKS.md +106 -0
- package/docs/PRIOR-WORKFLOW-EVALUATION.md +116 -0
- package/docs/PUBLICATION.md +28 -0
- package/docs/PYTEST.md +51 -0
- package/docs/RELEASE.md +115 -0
- package/docs/RENAMING.md +55 -0
- package/docs/REVIEW-EXCHANGE.md +136 -0
- package/docs/RUBY.md +50 -0
- package/docs/RUFF.md +39 -0
- package/docs/RUNTIME-INVENTORY.md +56 -0
- package/docs/RUST.md +74 -0
- package/docs/SARIF.md +64 -0
- package/docs/STATUS.md +530 -0
- package/docs/SWIFT.md +48 -0
- package/docs/TASK-STORAGE.md +141 -0
- package/docs/TOOL-IDENTITY.md +30 -0
- package/docs/TYPESCRIPT-BUILD.md +55 -0
- package/docs/VALIDATION-TASKS.md +119 -0
- package/docs/VITEST.md +36 -0
- package/docs/VUE-ROUTER.md +130 -0
- package/docs/VUE-TSC.md +26 -0
- package/docs/WORKSPACES.md +81 -0
- package/docs/licenses/nodable-entities-3.0.0.txt +21 -0
- package/docs/measurements/client-claude-before-schema-fix-darwin-arm64-node26.json +60 -0
- package/docs/measurements/client-claude-darwin-arm64-node26.json +55 -0
- package/docs/measurements/client-codex-darwin-arm64-node26.json +175 -0
- package/docs/measurements/evaluation-corpus-repo-verifier.json +332 -0
- package/docs/measurements/evaluation-darwin-arm64-node26.json +1236 -0
- package/docs/measurements/evaluation-linux-arm64-node22.json +1236 -0
- package/docs/measurements/external-eslint-darwin-arm64-node26.json +11866 -0
- package/docs/measurements/external-eslint-linux-arm64-node22.json +11866 -0
- package/docs/measurements/external-eslint-preservation.json +1527 -0
- package/docs/measurements/external-ruff-darwin-arm64-node26.json +1031 -0
- package/docs/measurements/external-ruff-linux-arm64-node22.json +1031 -0
- package/docs/measurements/hosted-ci-52ba415.json +1005 -0
- package/docs/measurements/impact-darwin-arm64-node26.json +6685 -0
- package/docs/measurements/impact-linux-arm64-node22.json +6690 -0
- package/docs/measurements/performance-darwin-arm64-node26.json +845 -0
- package/docs/measurements/performance-linux-arm64-node22.json +845 -0
- package/docs/measurements/required-native-profiles.json +500 -0
- package/package.json +72 -0
- package/packs/actionlint.json +8 -0
- package/packs/cpp.json +8 -0
- package/packs/dotnet.json +8 -0
- package/packs/go.json +7 -0
- package/packs/java.json +8 -0
- package/packs/javascript-node.json +7 -0
- package/packs/nuxt.json +7 -0
- package/packs/php-pest.json +7 -0
- package/packs/php-phpunit.json +7 -0
- package/packs/python-pytest.json +7 -0
- package/packs/ruby.json +7 -0
- package/packs/rust.json +7 -0
- package/packs/swift.json +7 -0
- package/packs/vue-router.json +7 -0
- package/packs/vue.json +11 -0
- package/schemas/actionlint-config.schema.json +19 -0
- package/schemas/architecture-policy.schema.json +51 -0
- package/schemas/architecture-report.schema.json +123 -0
- package/schemas/architecture-summary.schema.json +82 -0
- package/schemas/clang-database.schema.json +23 -0
- package/schemas/config.schema.json +69 -0
- package/schemas/contract-bundle.schema.json +78 -0
- package/schemas/contract-report.schema.json +114 -0
- package/schemas/contract-summary.schema.json +72 -0
- package/schemas/dependency-graph.schema.json +65 -0
- package/schemas/django-config.schema.json +16 -0
- package/schemas/dotnet-config.schema.json +60 -0
- package/schemas/external-manifest.schema.json +97 -0
- package/schemas/external-reference.schema.json +10 -0
- package/schemas/external-request.schema.json +56 -0
- package/schemas/external-result.schema.json +111 -0
- package/schemas/fastapi-config.schema.json +27 -0
- package/schemas/finding-baseline.schema.json +92 -0
- package/schemas/finding-comparison-summary.schema.json +95 -0
- package/schemas/finding-comparison.schema.json +121 -0
- package/schemas/guidance-context.schema.json +34 -0
- package/schemas/guidance-report.schema.json +176 -0
- package/schemas/guidance-summary.schema.json +68 -0
- package/schemas/java-config.schema.json +24 -0
- package/schemas/junit.schema.json +69 -0
- package/schemas/laravel-config.schema.json +11 -0
- package/schemas/mutation-recipe.schema.json +31 -0
- package/schemas/mutation-report.schema.json +238 -0
- package/schemas/mutation-summary.schema.json +98 -0
- package/schemas/nuxt-config.schema.json +51 -0
- package/schemas/plan-summary.schema.json +63 -0
- package/schemas/plan.schema.json +244 -0
- package/schemas/policy-pack.schema.json +41 -0
- package/schemas/report-summary.schema.json +100 -0
- package/schemas/report.schema.json +463 -0
- package/schemas/review-assessment.schema.json +133 -0
- package/schemas/review-context-summary.schema.json +31 -0
- package/schemas/review-context.schema.json +254 -0
- package/schemas/review-receipt-summary.schema.json +104 -0
- package/schemas/review-receipt.schema.json +271 -0
- package/schemas/review-selection.schema.json +30 -0
- package/schemas/runtime-comparison-summary.schema.json +68 -0
- package/schemas/runtime-comparison.schema.json +127 -0
- package/schemas/runtime-inventory.schema.json +99 -0
- package/schemas/vue-router-config.schema.json +67 -0
- package/server.json +47 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { validateWorkspace } from "./workspace.js";
|
|
2
|
+
import { environmentName } from "./environment.js";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { readProjectFile } from "./inventory.js";
|
|
6
|
+
import { loadPolicyPack, packReferenceSchema, } from "./policy-pack.js";
|
|
7
|
+
export const workspaceSchema = z.strictObject({
|
|
8
|
+
complete: z.boolean(),
|
|
9
|
+
dependencies: z
|
|
10
|
+
.array(z.strictObject({
|
|
11
|
+
consumer: z.string().min(1),
|
|
12
|
+
producer: z.string().min(1),
|
|
13
|
+
}))
|
|
14
|
+
.max(20_000),
|
|
15
|
+
});
|
|
16
|
+
export const configSchema = z.strictObject({
|
|
17
|
+
schemaVersion: z.literal(1),
|
|
18
|
+
workspace: workspaceSchema.optional(),
|
|
19
|
+
projects: z
|
|
20
|
+
.array(z.strictObject({
|
|
21
|
+
path: z.string().min(1),
|
|
22
|
+
checks: z.array(z.string().min(1)).max(256),
|
|
23
|
+
packs: z.array(packReferenceSchema).max(32).optional(),
|
|
24
|
+
environment: z.array(environmentName).max(64).optional(),
|
|
25
|
+
}))
|
|
26
|
+
.min(1),
|
|
27
|
+
});
|
|
28
|
+
export async function loadConfig(source, overlayPath) {
|
|
29
|
+
const legacyNames = new Set([
|
|
30
|
+
"repo-verifier.json",
|
|
31
|
+
...[
|
|
32
|
+
"actionlint",
|
|
33
|
+
"django",
|
|
34
|
+
"dotnet",
|
|
35
|
+
"fastapi",
|
|
36
|
+
"java",
|
|
37
|
+
"laravel",
|
|
38
|
+
"nuxt",
|
|
39
|
+
"vue-router",
|
|
40
|
+
].map((profile) => `repo-verifier.${profile}.json`),
|
|
41
|
+
]);
|
|
42
|
+
const legacy = source.files.find((file) => legacyNames.has(file.split("/").at(-1)));
|
|
43
|
+
if (legacy)
|
|
44
|
+
throw new Error(`Rename legacy configuration ${legacy} to its checktrail filename before validation`);
|
|
45
|
+
const raw = source.files.includes("checktrail.json")
|
|
46
|
+
? await readProjectFile(source.root, "checktrail.json")
|
|
47
|
+
: undefined;
|
|
48
|
+
const base = raw === undefined ? undefined : configSchema.parse(JSON.parse(raw));
|
|
49
|
+
const overlay = overlayPath === undefined
|
|
50
|
+
? undefined
|
|
51
|
+
: configSchema.parse(JSON.parse(await readProjectFile(source.root, overlayPath)));
|
|
52
|
+
if (overlay && !base)
|
|
53
|
+
throw new Error("Policy overlays require an explicit base checktrail.json");
|
|
54
|
+
const loaded = new Map();
|
|
55
|
+
const cache = new Map();
|
|
56
|
+
async function resolve(input) {
|
|
57
|
+
if (!input)
|
|
58
|
+
return undefined;
|
|
59
|
+
const paths = new Set();
|
|
60
|
+
for (const project of input.projects) {
|
|
61
|
+
if (paths.has(project.path))
|
|
62
|
+
throw new Error("Duplicate configured project path");
|
|
63
|
+
paths.add(project.path);
|
|
64
|
+
if (new Set(project.checks).size !== project.checks.length)
|
|
65
|
+
throw new Error("Duplicate check ID");
|
|
66
|
+
if (new Set(project.environment).size !== (project.environment?.length ?? 0))
|
|
67
|
+
throw new Error("Duplicate environment requirement");
|
|
68
|
+
const references = new Set();
|
|
69
|
+
for (const reference of project.packs ?? []) {
|
|
70
|
+
const key = JSON.stringify(reference);
|
|
71
|
+
if (!cache.has(key)) {
|
|
72
|
+
if (cache.size >= 64)
|
|
73
|
+
throw new Error("Policy pack reference limit exceeded");
|
|
74
|
+
cache.set(key, await loadPolicyPack(source.root, reference));
|
|
75
|
+
}
|
|
76
|
+
const { pack, sha256 } = cache.get(key);
|
|
77
|
+
if (references.has(pack.id))
|
|
78
|
+
throw new Error("Duplicate policy pack ID for project");
|
|
79
|
+
references.add(pack.id);
|
|
80
|
+
if (loaded.has(pack.id) && loaded.get(pack.id) !== sha256)
|
|
81
|
+
throw new Error("Conflicting policy pack versions or contents");
|
|
82
|
+
loaded.set(pack.id, sha256);
|
|
83
|
+
project.checks = [
|
|
84
|
+
...new Set([...project.checks, ...pack.requiredChecks]),
|
|
85
|
+
];
|
|
86
|
+
const names = [
|
|
87
|
+
...new Set([
|
|
88
|
+
...(project.environment ?? []),
|
|
89
|
+
...(pack.requiredEnvironment ?? []),
|
|
90
|
+
]),
|
|
91
|
+
];
|
|
92
|
+
if (names.length)
|
|
93
|
+
project.environment = names;
|
|
94
|
+
}
|
|
95
|
+
if (!project.checks.length)
|
|
96
|
+
throw new Error("Project policy has no required checks");
|
|
97
|
+
}
|
|
98
|
+
return input;
|
|
99
|
+
}
|
|
100
|
+
const resolvedBase = await resolve(base);
|
|
101
|
+
const resolvedOverlay = await resolve(overlay);
|
|
102
|
+
const config = resolvedBase ?? resolvedOverlay;
|
|
103
|
+
if (resolvedBase && resolvedOverlay) {
|
|
104
|
+
for (const extra of resolvedOverlay.projects) {
|
|
105
|
+
const existing = resolvedBase.projects.find((project) => project.path === extra.path);
|
|
106
|
+
if (!existing)
|
|
107
|
+
resolvedBase.projects.push(extra);
|
|
108
|
+
else {
|
|
109
|
+
existing.checks = [...new Set([...existing.checks, ...extra.checks])];
|
|
110
|
+
const names = [
|
|
111
|
+
...new Set([
|
|
112
|
+
...(existing.environment ?? []),
|
|
113
|
+
...(extra.environment ?? []),
|
|
114
|
+
]),
|
|
115
|
+
];
|
|
116
|
+
if (names.length)
|
|
117
|
+
existing.environment = names;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (resolvedOverlay.workspace) {
|
|
121
|
+
if (resolvedBase.workspace &&
|
|
122
|
+
JSON.stringify(resolvedBase.workspace) !==
|
|
123
|
+
JSON.stringify(resolvedOverlay.workspace))
|
|
124
|
+
throw new Error("Conflicting workspace declarations in policy overlay");
|
|
125
|
+
resolvedBase.workspace = resolvedOverlay.workspace;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (config?.workspace)
|
|
129
|
+
validateWorkspace(config.workspace, config.projects.map((project) => project.path));
|
|
130
|
+
return {
|
|
131
|
+
config: config === undefined ? undefined : configSchema.parse(config),
|
|
132
|
+
fingerprint: createHash("sha256")
|
|
133
|
+
.update(JSON.stringify({
|
|
134
|
+
config: config ?? { defaults: 1 },
|
|
135
|
+
packs: [...loaded].sort(),
|
|
136
|
+
overlay: overlayPath ?? null,
|
|
137
|
+
}))
|
|
138
|
+
.digest("hex"),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const contractBundleSchema: z.ZodObject<{
|
|
3
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
4
|
+
format: z.ZodLiteral<"contract-samples">;
|
|
5
|
+
capturedAt: z.ZodISODateTime;
|
|
6
|
+
contracts: z.ZodArray<z.ZodObject<{
|
|
7
|
+
id: z.ZodString;
|
|
8
|
+
producer: z.ZodObject<{
|
|
9
|
+
name: z.ZodString;
|
|
10
|
+
sourceFingerprint: z.ZodString;
|
|
11
|
+
}, z.core.$strict>;
|
|
12
|
+
consumer: z.ZodObject<{
|
|
13
|
+
name: z.ZodString;
|
|
14
|
+
sourceFingerprint: z.ZodString;
|
|
15
|
+
}, z.core.$strict>;
|
|
16
|
+
complete: z.ZodBoolean;
|
|
17
|
+
schema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
18
|
+
samples: z.ZodArray<z.ZodObject<{
|
|
19
|
+
name: z.ZodString;
|
|
20
|
+
payload: z.ZodUnknown;
|
|
21
|
+
}, z.core.$strict>>;
|
|
22
|
+
}, z.core.$strict>>;
|
|
23
|
+
}, z.core.$strict>;
|
|
24
|
+
export type ContractBundle = z.infer<typeof contractBundleSchema>;
|
|
25
|
+
export declare const contractReportSchema: z.ZodObject<{
|
|
26
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
27
|
+
engineVersion: z.ZodString;
|
|
28
|
+
provenance: z.ZodLiteral<"imported-contract-samples">;
|
|
29
|
+
outcome: z.ZodEnum<{
|
|
30
|
+
passed: "passed";
|
|
31
|
+
failed: "failed";
|
|
32
|
+
incomplete: "incomplete";
|
|
33
|
+
}>;
|
|
34
|
+
reason: z.ZodString;
|
|
35
|
+
artifactFingerprint: z.ZodString;
|
|
36
|
+
counts: z.ZodObject<{
|
|
37
|
+
contracts: z.ZodNumber;
|
|
38
|
+
passed: z.ZodNumber;
|
|
39
|
+
failed: z.ZodNumber;
|
|
40
|
+
unverified: z.ZodNumber;
|
|
41
|
+
samples: z.ZodNumber;
|
|
42
|
+
accepted: z.ZodNumber;
|
|
43
|
+
rejected: z.ZodNumber;
|
|
44
|
+
}, z.core.$strict>;
|
|
45
|
+
contracts: z.ZodArray<z.ZodObject<{
|
|
46
|
+
id: z.ZodString;
|
|
47
|
+
outcome: z.ZodEnum<{
|
|
48
|
+
passed: "passed";
|
|
49
|
+
failed: "failed";
|
|
50
|
+
incomplete: "incomplete";
|
|
51
|
+
}>;
|
|
52
|
+
reason: z.ZodString;
|
|
53
|
+
samples: z.ZodArray<z.ZodObject<{
|
|
54
|
+
name: z.ZodString;
|
|
55
|
+
accepted: z.ZodBoolean;
|
|
56
|
+
errors: z.ZodArray<z.ZodObject<{
|
|
57
|
+
keyword: z.ZodString;
|
|
58
|
+
instancePath: z.ZodString;
|
|
59
|
+
schemaPath: z.ZodString;
|
|
60
|
+
}, z.core.$strict>>;
|
|
61
|
+
}, z.core.$strict>>;
|
|
62
|
+
}, z.core.$strict>>;
|
|
63
|
+
}, z.core.$strict>;
|
|
64
|
+
export type ContractReport = z.infer<typeof contractReportSchema>;
|
|
65
|
+
export declare const contractSummarySchema: z.ZodObject<{
|
|
66
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
67
|
+
engineVersion: z.ZodString;
|
|
68
|
+
provenance: z.ZodLiteral<"imported-contract-samples">;
|
|
69
|
+
outcome: z.ZodEnum<{
|
|
70
|
+
passed: "passed";
|
|
71
|
+
failed: "failed";
|
|
72
|
+
incomplete: "incomplete";
|
|
73
|
+
}>;
|
|
74
|
+
reason: z.ZodString;
|
|
75
|
+
counts: z.ZodObject<{
|
|
76
|
+
contracts: z.ZodNumber;
|
|
77
|
+
passed: z.ZodNumber;
|
|
78
|
+
failed: z.ZodNumber;
|
|
79
|
+
unverified: z.ZodNumber;
|
|
80
|
+
samples: z.ZodNumber;
|
|
81
|
+
accepted: z.ZodNumber;
|
|
82
|
+
rejected: z.ZodNumber;
|
|
83
|
+
}, z.core.$strict>;
|
|
84
|
+
artifactFingerprint: z.ZodString;
|
|
85
|
+
}, z.core.$strict>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const name = z.string().min(1).max(1024);
|
|
3
|
+
const hash = z.string().regex(/^[a-f0-9]{64}$/);
|
|
4
|
+
const count = z.number().int().nonnegative();
|
|
5
|
+
export const contractBundleSchema = z.strictObject({
|
|
6
|
+
schemaVersion: z.literal(1),
|
|
7
|
+
format: z.literal("contract-samples"),
|
|
8
|
+
capturedAt: z.iso.datetime(),
|
|
9
|
+
contracts: z
|
|
10
|
+
.array(z.strictObject({
|
|
11
|
+
id: name,
|
|
12
|
+
producer: z.strictObject({ name, sourceFingerprint: hash }),
|
|
13
|
+
consumer: z.strictObject({ name, sourceFingerprint: hash }),
|
|
14
|
+
complete: z.boolean(),
|
|
15
|
+
schema: z.record(z.string(), z.unknown()),
|
|
16
|
+
samples: z
|
|
17
|
+
.array(z.strictObject({ name, payload: z.unknown() }))
|
|
18
|
+
.max(1000),
|
|
19
|
+
}))
|
|
20
|
+
.min(1)
|
|
21
|
+
.max(100),
|
|
22
|
+
});
|
|
23
|
+
export const contractReportSchema = z.strictObject({
|
|
24
|
+
schemaVersion: z.literal(1),
|
|
25
|
+
engineVersion: z.string(),
|
|
26
|
+
provenance: z.literal("imported-contract-samples"),
|
|
27
|
+
outcome: z.enum(["passed", "failed", "incomplete"]),
|
|
28
|
+
reason: z.string(),
|
|
29
|
+
artifactFingerprint: hash,
|
|
30
|
+
counts: z.strictObject({
|
|
31
|
+
contracts: count,
|
|
32
|
+
passed: count,
|
|
33
|
+
failed: count,
|
|
34
|
+
unverified: count,
|
|
35
|
+
samples: count,
|
|
36
|
+
accepted: count,
|
|
37
|
+
rejected: count,
|
|
38
|
+
}),
|
|
39
|
+
contracts: z.array(z.strictObject({
|
|
40
|
+
id: name,
|
|
41
|
+
outcome: z.enum(["passed", "failed", "incomplete"]),
|
|
42
|
+
reason: z.string(),
|
|
43
|
+
samples: z.array(z.strictObject({
|
|
44
|
+
name,
|
|
45
|
+
accepted: z.boolean(),
|
|
46
|
+
errors: z.array(z.strictObject({
|
|
47
|
+
keyword: z.string(),
|
|
48
|
+
instancePath: z.string(),
|
|
49
|
+
schemaPath: z.string(),
|
|
50
|
+
})),
|
|
51
|
+
})),
|
|
52
|
+
})),
|
|
53
|
+
});
|
|
54
|
+
export const contractSummarySchema = contractReportSchema.omit({
|
|
55
|
+
contracts: true,
|
|
56
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
2
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
3
|
+
import addFormatsImport from "ajv-formats";
|
|
4
|
+
import { VERSION } from "./types.js";
|
|
5
|
+
const addFormats = addFormatsImport.default ?? addFormatsImport;
|
|
6
|
+
const { bundle, fingerprint } = workerData;
|
|
7
|
+
function localSchema(value) {
|
|
8
|
+
if (value === null || typeof value !== "object")
|
|
9
|
+
return true;
|
|
10
|
+
for (const [key, item] of Object.entries(value)) {
|
|
11
|
+
if (["$ref", "$dynamicRef", "$recursiveRef"].includes(key) &&
|
|
12
|
+
(typeof item !== "string" || !item.startsWith("#")))
|
|
13
|
+
return false;
|
|
14
|
+
if (["$async", "$data", "nullable", "discriminator"].includes(key))
|
|
15
|
+
return false;
|
|
16
|
+
if ([
|
|
17
|
+
"$defs",
|
|
18
|
+
"definitions",
|
|
19
|
+
"properties",
|
|
20
|
+
"patternProperties",
|
|
21
|
+
"dependentSchemas",
|
|
22
|
+
].includes(key) &&
|
|
23
|
+
item !== null &&
|
|
24
|
+
typeof item === "object" &&
|
|
25
|
+
!Object.values(item).every(localSchema))
|
|
26
|
+
return false;
|
|
27
|
+
if (["allOf", "anyOf", "oneOf", "prefixItems"].includes(key) &&
|
|
28
|
+
Array.isArray(item) &&
|
|
29
|
+
!item.every(localSchema))
|
|
30
|
+
return false;
|
|
31
|
+
if ([
|
|
32
|
+
"items",
|
|
33
|
+
"additionalProperties",
|
|
34
|
+
"unevaluatedProperties",
|
|
35
|
+
"unevaluatedItems",
|
|
36
|
+
"contains",
|
|
37
|
+
"not",
|
|
38
|
+
"if",
|
|
39
|
+
"then",
|
|
40
|
+
"else",
|
|
41
|
+
"propertyNames",
|
|
42
|
+
].includes(key) &&
|
|
43
|
+
!localSchema(item))
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
const contracts = bundle.contracts.map((contract) => {
|
|
49
|
+
const incomplete = {
|
|
50
|
+
id: contract.id,
|
|
51
|
+
outcome: "incomplete",
|
|
52
|
+
reason: "Contract lacks complete samples or a supported strict JSON Schema 2020-12 definition.",
|
|
53
|
+
samples: [],
|
|
54
|
+
};
|
|
55
|
+
if (!contract.complete ||
|
|
56
|
+
!contract.samples.length ||
|
|
57
|
+
typeof contract.schema.type !== "string" ||
|
|
58
|
+
!localSchema(contract.schema))
|
|
59
|
+
return incomplete;
|
|
60
|
+
try {
|
|
61
|
+
const ajv = new Ajv2020({
|
|
62
|
+
strict: true,
|
|
63
|
+
strictRequired: true,
|
|
64
|
+
allErrors: false,
|
|
65
|
+
ownProperties: true,
|
|
66
|
+
logger: false,
|
|
67
|
+
coerceTypes: false,
|
|
68
|
+
useDefaults: false,
|
|
69
|
+
removeAdditional: false,
|
|
70
|
+
loopRequired: 32,
|
|
71
|
+
loopEnum: 32,
|
|
72
|
+
});
|
|
73
|
+
addFormats(ajv);
|
|
74
|
+
const validate = ajv.compile(contract.schema);
|
|
75
|
+
const samples = contract.samples.map((sample) => ({
|
|
76
|
+
name: sample.name,
|
|
77
|
+
accepted: validate(sample.payload) === true,
|
|
78
|
+
errors: (validate.errors ?? []).slice(0, 20).map((error) => ({
|
|
79
|
+
keyword: error.keyword,
|
|
80
|
+
instancePath: error.instancePath,
|
|
81
|
+
schemaPath: error.schemaPath,
|
|
82
|
+
})),
|
|
83
|
+
}));
|
|
84
|
+
const outcome = samples.some((sample) => !sample.accepted)
|
|
85
|
+
? "failed"
|
|
86
|
+
: "passed";
|
|
87
|
+
return {
|
|
88
|
+
id: contract.id,
|
|
89
|
+
outcome,
|
|
90
|
+
reason: outcome === "passed"
|
|
91
|
+
? "Captured producer samples satisfy the consumer schema."
|
|
92
|
+
: "A captured producer sample violates the consumer schema.",
|
|
93
|
+
samples,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return incomplete;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
const counts = {
|
|
101
|
+
contracts: contracts.length,
|
|
102
|
+
passed: contracts.filter((contract) => contract.outcome === "passed").length,
|
|
103
|
+
failed: contracts.filter((contract) => contract.outcome === "failed").length,
|
|
104
|
+
unverified: contracts.filter((contract) => contract.outcome === "incomplete")
|
|
105
|
+
.length,
|
|
106
|
+
samples: contracts.reduce((sum, contract) => sum + contract.samples.length, 0),
|
|
107
|
+
accepted: contracts.reduce((sum, contract) => sum + contract.samples.filter((sample) => sample.accepted).length, 0),
|
|
108
|
+
rejected: contracts.reduce((sum, contract) => sum + contract.samples.filter((sample) => !sample.accepted).length, 0),
|
|
109
|
+
};
|
|
110
|
+
parentPort?.postMessage({
|
|
111
|
+
schemaVersion: 1,
|
|
112
|
+
engineVersion: VERSION,
|
|
113
|
+
provenance: "imported-contract-samples",
|
|
114
|
+
outcome: counts.failed
|
|
115
|
+
? "failed"
|
|
116
|
+
: counts.unverified
|
|
117
|
+
? "incomplete"
|
|
118
|
+
: "passed",
|
|
119
|
+
reason: "Consumer schemas were checked against supplied producer samples; this does not prove live integration, source freshness or general schema compatibility.",
|
|
120
|
+
artifactFingerprint: fingerprint,
|
|
121
|
+
counts,
|
|
122
|
+
contracts,
|
|
123
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ContractReport } from "./contract-schema.js";
|
|
2
|
+
export declare function validateContracts(input: unknown, options?: {
|
|
3
|
+
timeoutMs?: number;
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
}): Promise<ContractReport>;
|
|
6
|
+
export declare function projectContractReport(result: ContractReport, detailed: boolean): {
|
|
7
|
+
schemaVersion: 1;
|
|
8
|
+
engineVersion: string;
|
|
9
|
+
provenance: "imported-contract-samples";
|
|
10
|
+
outcome: "passed" | "failed" | "incomplete";
|
|
11
|
+
reason: string;
|
|
12
|
+
counts: {
|
|
13
|
+
contracts: number;
|
|
14
|
+
passed: number;
|
|
15
|
+
failed: number;
|
|
16
|
+
unverified: number;
|
|
17
|
+
samples: number;
|
|
18
|
+
accepted: number;
|
|
19
|
+
rejected: number;
|
|
20
|
+
};
|
|
21
|
+
artifactFingerprint: string;
|
|
22
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { Worker } from "node:worker_threads";
|
|
3
|
+
import { contractBundleSchema, contractReportSchema, contractSummarySchema, } from "./contract-schema.js";
|
|
4
|
+
import { VERSION } from "./types.js";
|
|
5
|
+
function inputJSON(input) {
|
|
6
|
+
let count = 0;
|
|
7
|
+
const seen = new Set();
|
|
8
|
+
function visit(value, depth) {
|
|
9
|
+
if (++count > 100_000 || depth > 32)
|
|
10
|
+
throw new Error("Contract input exceeds structure limits");
|
|
11
|
+
if (value === null ||
|
|
12
|
+
typeof value === "string" ||
|
|
13
|
+
typeof value === "boolean" ||
|
|
14
|
+
(typeof value === "number" && Number.isFinite(value)))
|
|
15
|
+
return;
|
|
16
|
+
if (typeof value !== "object" || seen.has(value))
|
|
17
|
+
throw new Error("Contract input must be finite acyclic JSON");
|
|
18
|
+
seen.add(value);
|
|
19
|
+
if (!Array.isArray(value) &&
|
|
20
|
+
Object.getPrototypeOf(value) !== Object.prototype &&
|
|
21
|
+
Object.getPrototypeOf(value) !== null)
|
|
22
|
+
throw new Error("Contract input must contain plain JSON objects");
|
|
23
|
+
for (const item of Object.values(value))
|
|
24
|
+
visit(item, depth + 1);
|
|
25
|
+
seen.delete(value);
|
|
26
|
+
}
|
|
27
|
+
visit(input, 0);
|
|
28
|
+
const json = JSON.stringify(input);
|
|
29
|
+
if (Buffer.byteLength(json) > 8 * 1024 * 1024)
|
|
30
|
+
throw new Error("Contract input exceeds 8 MiB limit");
|
|
31
|
+
return json;
|
|
32
|
+
}
|
|
33
|
+
export async function validateContracts(input, options = {}) {
|
|
34
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
35
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 30_000)
|
|
36
|
+
throw new Error("Contract timeout must be between 1 and 30000 milliseconds");
|
|
37
|
+
const json = inputJSON(input);
|
|
38
|
+
const bundle = contractBundleSchema.parse(JSON.parse(json));
|
|
39
|
+
const ids = new Set();
|
|
40
|
+
for (const contract of bundle.contracts) {
|
|
41
|
+
if (ids.has(contract.id))
|
|
42
|
+
throw new Error("Duplicate contract ID");
|
|
43
|
+
ids.add(contract.id);
|
|
44
|
+
const samples = new Set();
|
|
45
|
+
for (const sample of contract.samples) {
|
|
46
|
+
if (samples.has(sample.name) || !Object.hasOwn(sample, "payload"))
|
|
47
|
+
throw new Error("Duplicate sample name or missing payload");
|
|
48
|
+
samples.add(sample.name);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const fingerprint = createHash("sha256").update(json).digest("hex");
|
|
52
|
+
const incomplete = () => ({
|
|
53
|
+
schemaVersion: 1,
|
|
54
|
+
engineVersion: VERSION,
|
|
55
|
+
provenance: "imported-contract-samples",
|
|
56
|
+
outcome: "incomplete",
|
|
57
|
+
reason: "Contract validation did not finish within its worker, time or cancellation limits.",
|
|
58
|
+
artifactFingerprint: fingerprint,
|
|
59
|
+
counts: {
|
|
60
|
+
contracts: bundle.contracts.length,
|
|
61
|
+
passed: 0,
|
|
62
|
+
failed: 0,
|
|
63
|
+
unverified: bundle.contracts.length,
|
|
64
|
+
samples: 0,
|
|
65
|
+
accepted: 0,
|
|
66
|
+
rejected: 0,
|
|
67
|
+
},
|
|
68
|
+
contracts: bundle.contracts.map((contract) => ({
|
|
69
|
+
id: contract.id,
|
|
70
|
+
outcome: "incomplete",
|
|
71
|
+
reason: "Worker validation did not complete.",
|
|
72
|
+
samples: [],
|
|
73
|
+
})),
|
|
74
|
+
});
|
|
75
|
+
if (options.signal?.aborted)
|
|
76
|
+
return incomplete();
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
const worker = new Worker(new URL("./contract-worker.js", import.meta.url), {
|
|
79
|
+
workerData: { bundle, fingerprint },
|
|
80
|
+
execArgv: [],
|
|
81
|
+
resourceLimits: {
|
|
82
|
+
maxOldGenerationSizeMb: 64,
|
|
83
|
+
maxYoungGenerationSizeMb: 16,
|
|
84
|
+
stackSizeMb: 4,
|
|
85
|
+
},
|
|
86
|
+
stdout: true,
|
|
87
|
+
stderr: true,
|
|
88
|
+
});
|
|
89
|
+
let settled = false;
|
|
90
|
+
const finish = (value) => {
|
|
91
|
+
if (settled)
|
|
92
|
+
return;
|
|
93
|
+
settled = true;
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
options.signal?.removeEventListener("abort", abort);
|
|
96
|
+
void worker.terminate().finally(() => resolve(value));
|
|
97
|
+
};
|
|
98
|
+
const abort = () => finish(incomplete());
|
|
99
|
+
const timer = setTimeout(abort, timeoutMs);
|
|
100
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
101
|
+
worker.once("message", (message) => {
|
|
102
|
+
const result = contractReportSchema.safeParse(message);
|
|
103
|
+
finish(result.success ? result.data : incomplete());
|
|
104
|
+
});
|
|
105
|
+
worker.once("error", abort);
|
|
106
|
+
worker.once("exit", abort);
|
|
107
|
+
if (options.signal?.aborted)
|
|
108
|
+
abort();
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
export function projectContractReport(result, detailed) {
|
|
112
|
+
if (detailed)
|
|
113
|
+
return result;
|
|
114
|
+
return contractSummarySchema.parse({
|
|
115
|
+
schemaVersion: result.schemaVersion,
|
|
116
|
+
engineVersion: result.engineVersion,
|
|
117
|
+
provenance: result.provenance,
|
|
118
|
+
outcome: result.outcome,
|
|
119
|
+
reason: result.reason,
|
|
120
|
+
artifactFingerprint: result.artifactFingerprint,
|
|
121
|
+
counts: result.counts,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runtimeInventorySchema } from "./runtime-inventory.js";
|
|
3
|
+
const count = z.number().int().nonnegative().max(20_000);
|
|
4
|
+
const schema = z.strictObject({
|
|
5
|
+
version: z.literal(1),
|
|
6
|
+
djangoVersion: z.literal("6.1.1"),
|
|
7
|
+
visitedNodes: count,
|
|
8
|
+
unsupportedNodes: count,
|
|
9
|
+
runtime: runtimeInventorySchema,
|
|
10
|
+
});
|
|
11
|
+
const attributesSchema = z.strictObject({
|
|
12
|
+
patterns: z.array(z.string()).min(1).max(33),
|
|
13
|
+
namespaces: z.array(z.string()),
|
|
14
|
+
handler: z.string().min(1),
|
|
15
|
+
name: z.string().nullable(),
|
|
16
|
+
defaultsHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
17
|
+
});
|
|
18
|
+
export function djangoEvidence(check, processes) {
|
|
19
|
+
const incomplete = {
|
|
20
|
+
status: "inconclusive",
|
|
21
|
+
reason: "Django URL inventory is empty, unsupported, malformed or incomplete.",
|
|
22
|
+
};
|
|
23
|
+
if (processes.length !== 1)
|
|
24
|
+
return incomplete;
|
|
25
|
+
if (processes[0].exitCode === 3) {
|
|
26
|
+
try {
|
|
27
|
+
z.strictObject({
|
|
28
|
+
unavailable: z.literal("django-runtime"),
|
|
29
|
+
reason: z.enum(["missing-package", "unsupported-version"]),
|
|
30
|
+
}).parse(JSON.parse(processes[0].stdout));
|
|
31
|
+
return {
|
|
32
|
+
status: "unavailable",
|
|
33
|
+
reason: "The pinned Django runtime profile is not installed.",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return incomplete;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (processes[0].exitCode !== 0)
|
|
41
|
+
return {
|
|
42
|
+
status: "error",
|
|
43
|
+
reason: "Django settings, setup, URL resolver or supported collector could not complete.",
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
const result = schema.parse(JSON.parse(processes[0].stdout));
|
|
47
|
+
const profile = JSON.parse(check.commands[0].args[2]);
|
|
48
|
+
const routes = result.runtime.collections[0];
|
|
49
|
+
if (result.runtime.sourceFingerprint !== check.commands[0].args[3] ||
|
|
50
|
+
result.runtime.producer.name !== "checktrail.django-routes" ||
|
|
51
|
+
result.runtime.producer.version !== "1.0.0" ||
|
|
52
|
+
result.runtime.assembly.name !== profile.assembly ||
|
|
53
|
+
result.runtime.assembly.environment !== profile.environment ||
|
|
54
|
+
result.runtime.collections.length !== 1 ||
|
|
55
|
+
routes?.kind !== "routes" ||
|
|
56
|
+
!routes.ordered ||
|
|
57
|
+
routes.complete !== (result.unsupportedNodes === 0) ||
|
|
58
|
+
!routes.entries.length ||
|
|
59
|
+
routes.entries.length + result.unsupportedNodes > result.visitedNodes)
|
|
60
|
+
return incomplete;
|
|
61
|
+
const identities = new Set();
|
|
62
|
+
let duplicates = 0;
|
|
63
|
+
for (const entry of routes.entries) {
|
|
64
|
+
const attributes = attributesSchema.parse(entry.attributes);
|
|
65
|
+
if (entry.key !== JSON.stringify(attributes.patterns))
|
|
66
|
+
return incomplete;
|
|
67
|
+
for (const pattern of attributes.patterns) {
|
|
68
|
+
const parsed = z
|
|
69
|
+
.tuple([
|
|
70
|
+
z.enum(["RoutePattern", "RegexPattern"]),
|
|
71
|
+
z.string(),
|
|
72
|
+
z.number().int().nonnegative(),
|
|
73
|
+
z.boolean(),
|
|
74
|
+
])
|
|
75
|
+
.parse(JSON.parse(pattern));
|
|
76
|
+
if (pattern !== JSON.stringify(parsed))
|
|
77
|
+
return incomplete;
|
|
78
|
+
}
|
|
79
|
+
if (identities.has(entry.key))
|
|
80
|
+
duplicates++;
|
|
81
|
+
identities.add(entry.key);
|
|
82
|
+
}
|
|
83
|
+
if (duplicates)
|
|
84
|
+
return {
|
|
85
|
+
status: "failed",
|
|
86
|
+
reason: "Django registered exact duplicate native URL pattern chains.",
|
|
87
|
+
runtime: result.runtime,
|
|
88
|
+
};
|
|
89
|
+
if (!routes.complete)
|
|
90
|
+
return { ...incomplete, runtime: result.runtime };
|
|
91
|
+
return {
|
|
92
|
+
status: "passed",
|
|
93
|
+
reason: "Django setup completed and supported native URL pattern chains contain no exact duplicates.",
|
|
94
|
+
runtime: result.runtime,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return incomplete;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const djangoRunner: string;
|