devcontour-harness 0.0.0-stage → 0.1.0-alpha.6
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/LICENSE +21 -0
- package/README.md +183 -2
- package/docs/architecture.md +473 -0
- package/docs/compatibility-versions.md +56 -0
- package/docs/compatibility.md +192 -0
- package/docs/connectors.md +323 -0
- package/docs/contracts.md +603 -0
- package/docs/integrations/hindsight.md +148 -0
- package/docs/quickstart.md +182 -0
- package/docs/release-notes/0.1.0-alpha.6.md +238 -0
- package/docs/review-probes.md +120 -0
- package/docs/review-protocol.md +251 -0
- package/docs/toolchains.md +167 -0
- package/docs/turbo-starter.md +269 -0
- package/docs/upgrade.md +202 -0
- package/examples/README.md +27 -0
- package/examples/external-connector/README.md +19 -0
- package/examples/external-connector/connector.mjs +55 -0
- package/examples/files-only/README.md +28 -0
- package/examples/files-only/context/norms/rules.md +4 -0
- package/examples/files-only/graph.json +23 -0
- package/examples/files-only/policy.json +30 -0
- package/examples/one-vendor/README.md +19 -0
- package/examples/one-vendor/policy.json +27 -0
- package/examples/single-task/README.md +18 -0
- package/examples/single-task/graph.json +27 -0
- package/examples/single-task/policy-same-vendor.json +55 -0
- package/examples/single-task/policy.json +52 -0
- package/examples/single-task/src/.gitkeep +0 -0
- package/examples/single-task/test/run.js +19 -0
- package/examples/toolchains/README.md +32 -0
- package/examples/toolchains/go/bad/label/label.go +9 -0
- package/examples/toolchains/go/good/label/label.go +9 -0
- package/examples/toolchains/go/policy.json +79 -0
- package/examples/toolchains/go/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/go/repo/_gitignore +1 -0
- package/examples/toolchains/go/repo/go.mod +5 -0
- package/examples/toolchains/go/repo/go.sum +2 -0
- package/examples/toolchains/go/repo/label/label.go +9 -0
- package/examples/toolchains/go/repo/label/label_test.go +27 -0
- package/examples/toolchains/go/repo/label/version.go +9 -0
- package/examples/toolchains/go/task.json +25 -0
- package/examples/toolchains/mixed-go/bad/backend/label/label.go +9 -0
- package/examples/toolchains/mixed-go/bad/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-go/good/backend/label/label.go +9 -0
- package/examples/toolchains/mixed-go/good/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-go/policy.json +126 -0
- package/examples/toolchains/mixed-go/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/mixed-go/repo/_gitignore +6 -0
- package/examples/toolchains/mixed-go/repo/backend/go.mod +3 -0
- package/examples/toolchains/mixed-go/repo/backend/label/label.go +9 -0
- package/examples/toolchains/mixed-go/repo/backend/label/label_test.go +30 -0
- package/examples/toolchains/mixed-go/repo/contract/check.mjs +17 -0
- package/examples/toolchains/mixed-go/repo/contract/label.json +9 -0
- package/examples/toolchains/mixed-go/repo/frontend/package.json +15 -0
- package/examples/toolchains/mixed-go/repo/frontend/pnpm-lock.yaml +22 -0
- package/examples/toolchains/mixed-go/repo/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-go/repo/frontend/test/label.test.js +11 -0
- package/examples/toolchains/mixed-go/task.json +25 -0
- package/examples/toolchains/mixed-python/bad/backend/src/fx_backend/__init__.py +6 -0
- package/examples/toolchains/mixed-python/bad/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-python/good/backend/src/fx_backend/__init__.py +6 -0
- package/examples/toolchains/mixed-python/good/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-python/policy.json +121 -0
- package/examples/toolchains/mixed-python/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/mixed-python/repo/_gitignore +6 -0
- package/examples/toolchains/mixed-python/repo/backend/.python-version +1 -0
- package/examples/toolchains/mixed-python/repo/backend/pyproject.toml +19 -0
- package/examples/toolchains/mixed-python/repo/backend/src/fx_backend/__init__.py +6 -0
- package/examples/toolchains/mixed-python/repo/backend/tests/test_label.py +11 -0
- package/examples/toolchains/mixed-python/repo/backend/uv.lock +108 -0
- package/examples/toolchains/mixed-python/repo/contract/check.mjs +17 -0
- package/examples/toolchains/mixed-python/repo/contract/label.json +9 -0
- package/examples/toolchains/mixed-python/repo/frontend/package.json +15 -0
- package/examples/toolchains/mixed-python/repo/frontend/pnpm-lock.yaml +22 -0
- package/examples/toolchains/mixed-python/repo/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-python/repo/frontend/test/label.test.js +11 -0
- package/examples/toolchains/mixed-python/task.json +25 -0
- package/examples/toolchains/npm/bad/src/label.js +4 -0
- package/examples/toolchains/npm/good/src/label.js +4 -0
- package/examples/toolchains/npm/policy.json +82 -0
- package/examples/toolchains/npm/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/npm/repo/_gitignore +4 -0
- package/examples/toolchains/npm/repo/package-lock.json +22 -0
- package/examples/toolchains/npm/repo/package.json +13 -0
- package/examples/toolchains/npm/repo/src/label.js +4 -0
- package/examples/toolchains/npm/repo/test/label.test.js +13 -0
- package/examples/toolchains/npm/task.json +25 -0
- package/examples/toolchains/pnpm/bad/packages/label/src/label.js +4 -0
- package/examples/toolchains/pnpm/good/packages/label/src/label.js +4 -0
- package/examples/toolchains/pnpm/policy.json +85 -0
- package/examples/toolchains/pnpm/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/pnpm/repo/_gitignore +4 -0
- package/examples/toolchains/pnpm/repo/package.json +10 -0
- package/examples/toolchains/pnpm/repo/packages/checker/check.mjs +5 -0
- package/examples/toolchains/pnpm/repo/packages/checker/package.json +9 -0
- package/examples/toolchains/pnpm/repo/packages/label/package.json +14 -0
- package/examples/toolchains/pnpm/repo/packages/label/src/label.js +4 -0
- package/examples/toolchains/pnpm/repo/packages/label/test/label.test.js +13 -0
- package/examples/toolchains/pnpm/repo/pnpm-lock.yaml +29 -0
- package/examples/toolchains/pnpm/repo/pnpm-workspace.yaml +2 -0
- package/examples/toolchains/pnpm/task.json +25 -0
- package/examples/toolchains/python/bad/src/fx_label/__init__.py +6 -0
- package/examples/toolchains/python/good/src/fx_label/__init__.py +6 -0
- package/examples/toolchains/python/policy.json +80 -0
- package/examples/toolchains/python/repo/.python-version +1 -0
- package/examples/toolchains/python/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/python/repo/_gitignore +5 -0
- package/examples/toolchains/python/repo/pyproject.toml +19 -0
- package/examples/toolchains/python/repo/src/fx_label/__init__.py +6 -0
- package/examples/toolchains/python/repo/tests/test_label.py +13 -0
- package/examples/toolchains/python/repo/uv.lock +108 -0
- package/examples/toolchains/python/task.json +25 -0
- package/examples/toolchains/turbo/bad/packages/label/src/label.js +4 -0
- package/examples/toolchains/turbo/good/packages/label/src/label.js +4 -0
- package/examples/toolchains/turbo/policy.json +110 -0
- package/examples/toolchains/turbo/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/turbo/repo/_gitignore +4 -0
- package/examples/toolchains/turbo/repo/package.json +13 -0
- package/examples/toolchains/turbo/repo/packages/checker/check.mjs +5 -0
- package/examples/toolchains/turbo/repo/packages/checker/package.json +9 -0
- package/examples/toolchains/turbo/repo/packages/label/package.json +14 -0
- package/examples/toolchains/turbo/repo/packages/label/src/label.js +4 -0
- package/examples/toolchains/turbo/repo/packages/label/test/label.test.js +13 -0
- package/examples/toolchains/turbo/repo/pnpm-lock.yaml +94 -0
- package/examples/toolchains/turbo/repo/pnpm-workspace.yaml +2 -0
- package/examples/toolchains/turbo/repo/turbo.json +7 -0
- package/examples/toolchains/turbo/task.json +25 -0
- package/examples/turbo-starter/README.md +16 -0
- package/examples/turbo-starter/changes/issued-at-consumer-only.patch +12 -0
- package/examples/turbo-starter/changes/issued-at.patch +130 -0
- package/examples/turbo-starter/cleanup.mjs +58 -0
- package/examples/turbo-starter/graph.json +32 -0
- package/examples/turbo-starter/harness/acceptance/db-isolation.test.mjs +97 -0
- package/examples/turbo-starter/harness/acceptance/env-probe.test.mjs +75 -0
- package/examples/turbo-starter/harness/acceptance/generation.test.mjs +54 -0
- package/examples/turbo-starter/harness/acceptance/issued-at.test.mjs +43 -0
- package/examples/turbo-starter/harness/acceptance/lifecycle-probe.test.mjs +47 -0
- package/examples/turbo-starter/harness/compose-bridge.mjs +593 -0
- package/examples/turbo-starter/harness/compose.acceptance.yml +23 -0
- package/examples/turbo-starter/harness/env/backend.env.template +7 -0
- package/examples/turbo-starter/harness/env/compose.env.template +6 -0
- package/examples/turbo-starter/policy.json +109 -0
- package/examples/turbo-starter/policy.lifecycle.json +114 -0
- package/examples/turbo-starter/setup.mjs +352 -0
- package/examples/two-vendors/policy.json +30 -0
- package/lib/agents/cli-adapter.d.ts +48 -0
- package/lib/agents/cli-adapter.js +386 -0
- package/lib/agents/inspect.d.ts +6 -0
- package/lib/agents/inspect.js +46 -0
- package/lib/agents/probe-server.d.ts +2 -0
- package/lib/agents/probe-server.js +13 -0
- package/lib/agents/review-probe.d.ts +295 -0
- package/lib/agents/review-probe.js +872 -0
- package/lib/agents/runtime-events.d.ts +11 -0
- package/lib/agents/runtime-events.js +21 -0
- package/lib/agents/schemas.d.ts +217 -0
- package/lib/agents/schemas.js +124 -0
- package/lib/agents/usage.d.ts +8 -0
- package/lib/agents/usage.js +83 -0
- package/lib/cli.d.ts +2 -0
- package/lib/cli.js +491 -0
- package/lib/connectors/files.d.ts +108 -0
- package/lib/connectors/files.js +133 -0
- package/lib/contracts/checks.d.ts +307 -0
- package/lib/contracts/checks.js +181 -0
- package/lib/contracts/cli.d.ts +3895 -0
- package/lib/contracts/cli.js +664 -0
- package/lib/contracts/context.d.ts +135 -0
- package/lib/contracts/context.js +129 -0
- package/lib/contracts/digest.d.ts +9 -0
- package/lib/contracts/digest.js +22 -0
- package/lib/contracts/errors.d.ts +81 -0
- package/lib/contracts/errors.js +70 -0
- package/lib/contracts/events.d.ts +827 -0
- package/lib/contracts/events.js +258 -0
- package/lib/contracts/execution.d.ts +3216 -0
- package/lib/contracts/execution.js +872 -0
- package/lib/contracts/failure.d.ts +67 -0
- package/lib/contracts/failure.js +127 -0
- package/lib/contracts/index.d.ts +18 -0
- package/lib/contracts/index.js +18 -0
- package/lib/contracts/policy.d.ts +1080 -0
- package/lib/contracts/policy.js +544 -0
- package/lib/contracts/ports.d.ts +417 -0
- package/lib/contracts/ports.js +1 -0
- package/lib/contracts/primitives.d.ts +63 -0
- package/lib/contracts/primitives.js +73 -0
- package/lib/contracts/profiles.d.ts +336 -0
- package/lib/contracts/profiles.js +109 -0
- package/lib/contracts/published.d.ts +3237 -0
- package/lib/contracts/published.js +34 -0
- package/lib/contracts/result.d.ts +1280 -0
- package/lib/contracts/result.js +311 -0
- package/lib/contracts/review.d.ts +441 -0
- package/lib/contracts/review.js +477 -0
- package/lib/contracts/toolchain.d.ts +96 -0
- package/lib/contracts/toolchain.js +89 -0
- package/lib/contracts/version.d.ts +17 -0
- package/lib/contracts/version.js +17 -0
- package/lib/contracts/work.d.ts +173 -0
- package/lib/contracts/work.js +178 -0
- package/lib/control/base-move.d.ts +44 -0
- package/lib/control/base-move.js +90 -0
- package/lib/control/doctor.d.ts +50 -0
- package/lib/control/doctor.js +113 -0
- package/lib/control/harness.d.ts +1125 -0
- package/lib/control/harness.js +741 -0
- package/lib/control/provenance.d.ts +17 -0
- package/lib/control/provenance.js +25 -0
- package/lib/control/recovery.d.ts +237 -0
- package/lib/control/recovery.js +236 -0
- package/lib/control/review.d.ts +62 -0
- package/lib/control/review.js +178 -0
- package/lib/control/workflow.d.ts +247 -0
- package/lib/control/workflow.js +259 -0
- package/lib/engine/budget.d.ts +345 -0
- package/lib/engine/budget.js +278 -0
- package/lib/engine/classify.d.ts +8 -0
- package/lib/engine/classify.js +32 -0
- package/lib/engine/engine.d.ts +1527 -0
- package/lib/engine/engine.js +2259 -0
- package/lib/engine/failure.d.ts +1 -0
- package/lib/engine/failure.js +3 -0
- package/lib/engine/joint-runner.d.ts +257 -0
- package/lib/engine/joint-runner.js +254 -0
- package/lib/engine/measured.d.ts +15 -0
- package/lib/engine/measured.js +82 -0
- package/lib/engine/prompt.d.ts +66 -0
- package/lib/engine/prompt.js +131 -0
- package/lib/engine/property-recovery.d.ts +50 -0
- package/lib/engine/property-recovery.js +171 -0
- package/lib/engine/repair.d.ts +33 -0
- package/lib/engine/repair.js +82 -0
- package/lib/engine/result.d.ts +9 -0
- package/lib/engine/result.js +218 -0
- package/lib/engine/reuse.d.ts +80 -0
- package/lib/engine/reuse.js +102 -0
- package/lib/engine/runner.d.ts +360 -0
- package/lib/engine/runner.js +1836 -0
- package/lib/engine/standalone.d.ts +6 -0
- package/lib/engine/standalone.js +48 -0
- package/lib/engine/usage.d.ts +87 -0
- package/lib/engine/usage.js +165 -0
- package/lib/environment/isolation.d.ts +110 -0
- package/lib/environment/isolation.js +238 -0
- package/lib/environment/lifecycle.d.ts +40 -0
- package/lib/environment/lifecycle.js +197 -0
- package/lib/environment/live-calls.d.ts +14 -0
- package/lib/environment/live-calls.js +73 -0
- package/lib/environment/os-tools.d.ts +16 -0
- package/lib/environment/os-tools.js +35 -0
- package/lib/environment/process.d.ts +32 -0
- package/lib/environment/process.js +369 -0
- package/lib/environment/redaction.d.ts +22 -0
- package/lib/environment/redaction.js +102 -0
- package/lib/environment/resources.d.ts +31 -0
- package/lib/environment/resources.js +120 -0
- package/lib/index.d.ts +65 -0
- package/lib/index.js +67 -0
- package/lib/integrations/connector/commands.d.ts +358 -0
- package/lib/integrations/connector/commands.js +428 -0
- package/lib/integrations/connector/config.d.ts +215 -0
- package/lib/integrations/connector/config.js +214 -0
- package/lib/integrations/connector/delivery.d.ts +107 -0
- package/lib/integrations/connector/delivery.js +591 -0
- package/lib/integrations/connector/fake.d.ts +291 -0
- package/lib/integrations/connector/fake.js +786 -0
- package/lib/integrations/connector/history.d.ts +124 -0
- package/lib/integrations/connector/history.js +497 -0
- package/lib/integrations/connector/index.d.ts +16 -0
- package/lib/integrations/connector/index.js +16 -0
- package/lib/integrations/connector/redact.d.ts +19 -0
- package/lib/integrations/connector/redact.js +91 -0
- package/lib/integrations/connector/registry.d.ts +15 -0
- package/lib/integrations/connector/registry.js +43 -0
- package/lib/integrations/connector/snapshot.d.ts +131 -0
- package/lib/integrations/connector/snapshot.js +422 -0
- package/lib/integrations/connector/source.d.ts +37 -0
- package/lib/integrations/connector/source.js +73 -0
- package/lib/integrations/connector/steps.d.ts +71 -0
- package/lib/integrations/connector/steps.js +188 -0
- package/lib/integrations/connector/store.d.ts +140 -0
- package/lib/integrations/connector/store.js +277 -0
- package/lib/integrations/connector/summary.d.ts +38 -0
- package/lib/integrations/connector/summary.js +59 -0
- package/lib/integrations/connector/testing-worker.d.ts +1 -0
- package/lib/integrations/connector/testing-worker.js +81 -0
- package/lib/integrations/connector/testing.d.ts +205 -0
- package/lib/integrations/connector/testing.js +1615 -0
- package/lib/integrations/connector/transport.d.ts +240 -0
- package/lib/integrations/connector/transport.js +28 -0
- package/lib/integrations/memory/context.d.ts +120 -0
- package/lib/integrations/memory/context.js +239 -0
- package/lib/integrations/memory/index.d.ts +8 -0
- package/lib/integrations/memory/index.js +8 -0
- package/lib/integrations/memory/provider.d.ts +79 -0
- package/lib/integrations/memory/provider.js +22 -0
- package/lib/integrations/memory/sink.d.ts +56 -0
- package/lib/integrations/memory/sink.js +116 -0
- package/lib/integrations/memory/testing.d.ts +44 -0
- package/lib/integrations/memory/testing.js +61 -0
- package/lib/integrations/relay-v1/index.d.ts +122 -0
- package/lib/integrations/relay-v1/index.js +860 -0
- package/lib/integrations/relay-v2/index.d.ts +145 -0
- package/lib/integrations/relay-v2/index.js +752 -0
- package/lib/integrations/relay-v2/process.d.ts +32 -0
- package/lib/integrations/relay-v2/process.js +112 -0
- package/lib/integrations/relay-v2/protocol.d.ts +172 -0
- package/lib/integrations/relay-v2/protocol.js +142 -0
- package/lib/package.d.ts +2 -0
- package/lib/package.js +3 -0
- package/lib/profiles/resolve.d.ts +190 -0
- package/lib/profiles/resolve.js +273 -0
- package/lib/storage/document.d.ts +1234 -0
- package/lib/storage/document.js +80 -0
- package/lib/storage/sqlite.d.ts +35 -0
- package/lib/storage/sqlite.js +260 -0
- package/lib/testing/index.d.ts +5 -0
- package/lib/testing/index.js +5 -0
- package/lib/testing/store-contract.d.ts +8 -0
- package/lib/testing/store-contract.js +167 -0
- package/lib/toolchains/index.d.ts +9 -0
- package/lib/toolchains/index.js +9 -0
- package/lib/toolchains/inspect.d.ts +105 -0
- package/lib/toolchains/inspect.js +831 -0
- package/lib/toolchains/manifests.d.ts +67 -0
- package/lib/toolchains/manifests.js +238 -0
- package/lib/toolchains/probe.d.ts +28 -0
- package/lib/toolchains/probe.js +54 -0
- package/lib/toolchains/recipes.d.ts +33 -0
- package/lib/toolchains/recipes.js +51 -0
- package/lib/validate.d.ts +36 -0
- package/lib/validate.js +175 -0
- package/lib/verification/acceptance.d.ts +284 -0
- package/lib/verification/acceptance.js +670 -0
- package/lib/verification/checks.d.ts +72 -0
- package/lib/verification/checks.js +190 -0
- package/lib/verification/confirm.d.ts +125 -0
- package/lib/verification/confirm.js +411 -0
- package/lib/verification/corpus.d.ts +1303 -0
- package/lib/verification/corpus.js +744 -0
- package/lib/verification/dependencies.d.ts +66 -0
- package/lib/verification/dependencies.js +144 -0
- package/lib/verification/gates.d.ts +160 -0
- package/lib/verification/gates.js +600 -0
- package/lib/verification/joint.d.ts +63 -0
- package/lib/verification/joint.js +121 -0
- package/lib/verification/proof.d.ts +54 -0
- package/lib/verification/proof.js +96 -0
- package/lib/verification/property.d.ts +86 -0
- package/lib/verification/property.js +214 -0
- package/lib/verification/requirements.d.ts +25 -0
- package/lib/verification/requirements.js +69 -0
- package/lib/verification/self-check.d.ts +29 -0
- package/lib/verification/self-check.js +99 -0
- package/lib/verification/spike.d.ts +79 -0
- package/lib/verification/spike.js +124 -0
- package/package.json +100 -4
- package/profiles/expo-mobile.json +15 -0
- package/profiles/go-api.json +15 -0
- package/profiles/go-gotestsum.json +21 -0
- package/profiles/mobile-maestro.json +29 -0
- package/profiles/nest-api.json +26 -0
- package/profiles/next-product.json +72 -0
- package/profiles/node-checks.json +77 -0
- package/profiles/npm-ci.json +22 -0
- package/profiles/pnpm-frozen.json +24 -0
- package/profiles/python-api.json +32 -0
- package/profiles/python-uv.json +26 -0
- package/profiles/react-native.json +50 -0
- package/profiles/react-vite-admin.json +71 -0
- package/schemas/context-snapshot.schema.json +231 -0
- package/schemas/contract-document.schema.json +385 -0
- package/schemas/execution-document.schema.json +3695 -0
- package/schemas/execution-policy.schema.json +2196 -0
- package/schemas/joint-verification.schema.json +617 -0
- package/schemas/result-envelope.schema.json +1652 -0
- package/schemas/review-record.schema.json +177 -0
- package/schemas/run-event.schema.json +2458 -0
- package/schemas/run-result.schema.json +1570 -0
- package/schemas/stack-profile.schema.json +535 -0
- package/schemas/work-graph.schema.json +381 -0
- package/schemas/work-order.schema.json +256 -0
- package/skills/harness/SKILL.md +64 -0
- package/templates/AGENTS.md +13 -0
- package/templates/controller-mode/AGENTS.controller.md +58 -0
- package/templates/controller-mode/README.md +44 -0
- package/templates/graph.json +22 -0
- package/templates/policy.json +28 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { HarnessError, PROFILE_DEPTH_LIMIT, PROFILE_FILE_LIMIT, PROFILE_FILES_LIMIT, gateOrderIssues, gateSchema, repositoryPolicySchema, roleBindingSchema, stackProfileSchema, } from "../contracts/index.js";
|
|
6
|
+
import { REQUIREMENT_GATE } from "../verification/requirements.js";
|
|
7
|
+
/** Встроенные профили пакета: `profiles/<id>.json` рядом с `lib/`. */
|
|
8
|
+
export const builtinProfilesRoot = fileURLToPath(new URL('../../profiles/', import.meta.url));
|
|
9
|
+
const builtinId = /^[A-Za-z0-9_-]{1,80}$/;
|
|
10
|
+
const sha256 = (value) => createHash('sha256').update(value).digest('hex');
|
|
11
|
+
const refuse = (message) => {
|
|
12
|
+
throw new HarnessError('INVALID_INPUT', message);
|
|
13
|
+
};
|
|
14
|
+
/** ID встроенных профилей пакета. */
|
|
15
|
+
export function builtinProfiles(root = builtinProfilesRoot) {
|
|
16
|
+
return readdirSync(root)
|
|
17
|
+
.filter((f) => f.endsWith('.json'))
|
|
18
|
+
.map((f) => f.slice(0, -'.json'.length))
|
|
19
|
+
.sort();
|
|
20
|
+
}
|
|
21
|
+
/** Файл профиля: внутри своего корня, без symlink на пути, обычный файл до 128 KiB. */
|
|
22
|
+
function safeFile(root, file) {
|
|
23
|
+
const path = relative(root, file);
|
|
24
|
+
if (!path || isAbsolute(path) || path === '..' || path.startsWith('..' + sep))
|
|
25
|
+
refuse('Профиль должен находиться внутри репозитория');
|
|
26
|
+
let cursor = root;
|
|
27
|
+
for (const part of path.split(sep)) {
|
|
28
|
+
cursor = join(cursor, part);
|
|
29
|
+
let stat;
|
|
30
|
+
try {
|
|
31
|
+
stat = lstatSync(cursor);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return refuse('Профиль не найден: ' + path);
|
|
35
|
+
}
|
|
36
|
+
if (stat.isSymbolicLink())
|
|
37
|
+
refuse('Профиль не допускает symlink: ' + path);
|
|
38
|
+
}
|
|
39
|
+
const stat = lstatSync(file);
|
|
40
|
+
if (!stat.isFile() || stat.size > PROFILE_FILE_LIMIT)
|
|
41
|
+
refuse('Профиль требует файл до 128 KiB');
|
|
42
|
+
return readFileSync(file, 'utf8');
|
|
43
|
+
}
|
|
44
|
+
function parseManifest(content, name) {
|
|
45
|
+
let raw;
|
|
46
|
+
try {
|
|
47
|
+
raw = JSON.parse(content);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return refuse(`Профиль ${name}: не JSON`);
|
|
51
|
+
}
|
|
52
|
+
const parsed = stackProfileSchema.safeParse(raw);
|
|
53
|
+
if (!parsed.success)
|
|
54
|
+
refuse(`Профиль ${name}: ` +
|
|
55
|
+
parsed.error.issues
|
|
56
|
+
.map((i) => `${i.path.join('.') || '(корень)'}: ${i.message}`)
|
|
57
|
+
.join('; '));
|
|
58
|
+
return parsed.data;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Разрешение профиля: встроенный ID или относительный `./file.json` внутри
|
|
62
|
+
* репозитория, слои `extends` в глубину, без циклов и не больше 32 файлов.
|
|
63
|
+
* Только чтение манифестов — ни одна команда проекта не исполняется.
|
|
64
|
+
*
|
|
65
|
+
* Слои складываются: gates, подготовка, возможности, защищённые и
|
|
66
|
+
* генерируемые пути — объединением; окружение и lifecycle — только из одного
|
|
67
|
+
* слоя (иначе конфликт); параллельность — наименьшая, время попытки —
|
|
68
|
+
* наибольшее; роли и инструкции стека — по ID, надстройка уточняет базу.
|
|
69
|
+
* Итоговый профиль обязан иметь test gate с JUnit: зелёный выход без отчёта
|
|
70
|
+
* ничего не доказывает. Файлы локальных слоёв становятся защищёнными путями.
|
|
71
|
+
*/
|
|
72
|
+
export function resolveProfile(ref, options = {}) {
|
|
73
|
+
const builtinRoot = realpathSync(options.builtinRoot ?? builtinProfilesRoot);
|
|
74
|
+
const root = options.repository ? realpathSync(options.repository) : undefined;
|
|
75
|
+
const repositoryId = options.repositoryId ?? 'main';
|
|
76
|
+
const active = new Set();
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
const closure = [];
|
|
79
|
+
const layers = [];
|
|
80
|
+
const files = [];
|
|
81
|
+
let source;
|
|
82
|
+
let leafContent = '';
|
|
83
|
+
const visit = (reference, parent, depth = 0) => {
|
|
84
|
+
if (depth > PROFILE_DEPTH_LIMIT)
|
|
85
|
+
refuse('Слишком глубокое наследование профилей');
|
|
86
|
+
const builtin = builtinId.test(reference);
|
|
87
|
+
if (!builtin && !reference.startsWith('./') && !reference.startsWith('../'))
|
|
88
|
+
refuse('Профиль: укажите встроенный ID либо относительный ./file.json');
|
|
89
|
+
if (!builtin && (!root || parent === 'builtin'))
|
|
90
|
+
refuse('Локальный профиль требует корень репозитория');
|
|
91
|
+
const file = builtin
|
|
92
|
+
? join(builtinRoot, reference + '.json')
|
|
93
|
+
: resolve(parent ?? root, reference);
|
|
94
|
+
const owner = builtin ? builtinRoot : root;
|
|
95
|
+
const key = builtin ? 'builtin:' + reference : 'local:' + relative(root, file);
|
|
96
|
+
if (active.has(key))
|
|
97
|
+
refuse('Цикл наследования профилей: ' + key);
|
|
98
|
+
const content = safeFile(owner, file);
|
|
99
|
+
const manifest = parseManifest(content, key);
|
|
100
|
+
if (builtin && manifest.id !== reference)
|
|
101
|
+
refuse('ID встроенного профиля не совпадает: ' + key);
|
|
102
|
+
if (depth === 0) {
|
|
103
|
+
leafContent = content;
|
|
104
|
+
if (!builtin)
|
|
105
|
+
source = { repositoryId, path: relative(root, file) };
|
|
106
|
+
}
|
|
107
|
+
if (seen.has(key))
|
|
108
|
+
return manifest;
|
|
109
|
+
if (seen.size + active.size >= PROFILE_FILES_LIMIT)
|
|
110
|
+
refuse(`Не более ${PROFILE_FILES_LIMIT} файлов профиля`);
|
|
111
|
+
active.add(key);
|
|
112
|
+
for (const base of manifest.extends)
|
|
113
|
+
visit(base, builtin ? 'builtin' : dirname(file), depth + 1);
|
|
114
|
+
active.delete(key);
|
|
115
|
+
seen.add(key);
|
|
116
|
+
closure.push({ ref: key, digest: sha256(content) });
|
|
117
|
+
if (!builtin)
|
|
118
|
+
files.push(relative(root, file));
|
|
119
|
+
layers.push(manifest);
|
|
120
|
+
return manifest;
|
|
121
|
+
};
|
|
122
|
+
const leaf = visit(ref);
|
|
123
|
+
const declared = layers.flatMap((p) => p.gates);
|
|
124
|
+
const order = gateOrderIssues(declared);
|
|
125
|
+
if (order.length)
|
|
126
|
+
refuse(order[0].message);
|
|
127
|
+
for (const gate of declared) {
|
|
128
|
+
if (gate.id === REQUIREMENT_GATE)
|
|
129
|
+
refuse('Зарезервированный gate: ' + REQUIREMENT_GATE);
|
|
130
|
+
if (gate.kind === 'test' && !gate.report)
|
|
131
|
+
refuse('Test gate требует JUnit: ' + gate.id);
|
|
132
|
+
}
|
|
133
|
+
const gates = declared.map((g) => gateSchema.parse(g));
|
|
134
|
+
if (!gates.some((g) => g.kind === 'test' && g.report))
|
|
135
|
+
refuse('Итоговый профиль требует test gate с JUnit');
|
|
136
|
+
const prepare = layers.flatMap((p) => p.prepare);
|
|
137
|
+
if (new Set(prepare.map((s) => s.id)).size !== prepare.length)
|
|
138
|
+
refuse('Повтор prepare step ID');
|
|
139
|
+
const single = (key) => {
|
|
140
|
+
const values = layers.map((p) => p[key]).filter((v) => v !== undefined);
|
|
141
|
+
if (values.length > 1)
|
|
142
|
+
refuse('Конфликт частей профиля: ' + key);
|
|
143
|
+
return values[0];
|
|
144
|
+
};
|
|
145
|
+
const environment = single('environment');
|
|
146
|
+
const lifecycle = single('lifecycle');
|
|
147
|
+
const toolchain = mergeToolchains(layers);
|
|
148
|
+
for (const steps of lifecycle ? [lifecycle.setup, lifecycle.ready, lifecycle.teardown] : [])
|
|
149
|
+
if (new Set(steps.map((s) => s.id)).size !== steps.length)
|
|
150
|
+
refuse('Повтор lifecycle step ID');
|
|
151
|
+
const limits = layers.flatMap((p) => (p.concurrency ? [p.concurrency] : []));
|
|
152
|
+
const timeouts = layers.flatMap((p) => (p.runTimeoutMs ? [p.runTimeoutMs] : []));
|
|
153
|
+
return {
|
|
154
|
+
id: leaf.id,
|
|
155
|
+
version: leaf.version,
|
|
156
|
+
// Один встроенный файл — digest его байтов: так закреплены прежние
|
|
157
|
+
// потребители; составной или локальный — digest замыкания слоёв.
|
|
158
|
+
digest: closure.length === 1 && !source ? sha256(leafContent) : sha256(JSON.stringify(closure)),
|
|
159
|
+
capabilities: [...new Set(layers.flatMap((p) => p.capabilities))],
|
|
160
|
+
gates,
|
|
161
|
+
prepare,
|
|
162
|
+
...(environment ? { environment } : {}),
|
|
163
|
+
...(lifecycle ? { lifecycle } : {}),
|
|
164
|
+
...(toolchain ? { toolchain } : {}),
|
|
165
|
+
protectedPaths: [...new Set([...layers.flatMap((p) => p.protectedPaths), ...files])],
|
|
166
|
+
generatedPaths: [...new Set(layers.flatMap((p) => p.generatedPaths))],
|
|
167
|
+
...(limits.length ? { concurrency: Math.min(...limits) } : {}),
|
|
168
|
+
...(timeouts.length ? { runTimeoutMs: Math.max(...timeouts) } : {}),
|
|
169
|
+
roles: Object.assign({}, ...layers.map((p) => p.roles)),
|
|
170
|
+
contextPacks: [
|
|
171
|
+
...new Map(layers.flatMap((p) => p.contextPacks).map((pack) => [pack.id, pack])).values(),
|
|
172
|
+
],
|
|
173
|
+
...(source ? { source } : {}),
|
|
174
|
+
closure,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Требования toolchain слоёв: инструменты и выбор менеджера пакетов
|
|
179
|
+
* объединяются, но одно имя с разными значениями — конфликт композиции, а не
|
|
180
|
+
* молчаливая победа последнего слоя.
|
|
181
|
+
*/
|
|
182
|
+
function mergeToolchains(layers) {
|
|
183
|
+
const present = layers.flatMap((p) => (p.toolchain ? [p.toolchain] : []));
|
|
184
|
+
if (!present.length)
|
|
185
|
+
return undefined;
|
|
186
|
+
const merged = {
|
|
187
|
+
tools: {},
|
|
188
|
+
packageManagers: {},
|
|
189
|
+
};
|
|
190
|
+
for (const t of present) {
|
|
191
|
+
for (const [tool, version] of Object.entries(t.tools)) {
|
|
192
|
+
if (merged.tools[tool] !== undefined && merged.tools[tool] !== version)
|
|
193
|
+
refuse(`Конфликт частей профиля: toolchain.tools.${tool} (${merged.tools[tool]} и ${version})`);
|
|
194
|
+
merged.tools[tool] = version;
|
|
195
|
+
}
|
|
196
|
+
for (const [dir, manager] of Object.entries(t.packageManagers)) {
|
|
197
|
+
if (merged.packageManagers[dir] !== undefined && merged.packageManagers[dir] !== manager)
|
|
198
|
+
refuse(`Конфликт частей профиля: toolchain.packageManagers.${dir} (${merged.packageManagers[dir]} и ${manager})`);
|
|
199
|
+
merged.packageManagers[dir] = manager;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return merged;
|
|
203
|
+
}
|
|
204
|
+
export const profilePin = (p) => ({
|
|
205
|
+
id: p.id,
|
|
206
|
+
version: p.version,
|
|
207
|
+
digest: p.digest,
|
|
208
|
+
...(p.source ? { source: p.source } : {}),
|
|
209
|
+
});
|
|
210
|
+
/** Поля репозитория, которые даёт профиль. */
|
|
211
|
+
const profiledKeys = [
|
|
212
|
+
'gates',
|
|
213
|
+
'prepare',
|
|
214
|
+
'environment',
|
|
215
|
+
'lifecycle',
|
|
216
|
+
'protectedPaths',
|
|
217
|
+
'generatedPaths',
|
|
218
|
+
'toolchain',
|
|
219
|
+
];
|
|
220
|
+
/**
|
|
221
|
+
* Описание репозитория политики из профиля. Явные значения `own` сильнее
|
|
222
|
+
* профиля и ничего не дополняют молча: `shadowed` называет поля, которые
|
|
223
|
+
* профиль задаёт иначе, а `own` переопределяет, — иначе обновлённый профиль с
|
|
224
|
+
* новым gate «применялся бы», не доходя до политики.
|
|
225
|
+
*/
|
|
226
|
+
export function repositoryFromProfile(profile, own) {
|
|
227
|
+
const offered = (key) => {
|
|
228
|
+
const value = profile[key];
|
|
229
|
+
return Array.isArray(value) ? value.length > 0 : value !== undefined;
|
|
230
|
+
};
|
|
231
|
+
const fromProfile = repositoryPolicySchema.parse({
|
|
232
|
+
gates: profile.gates,
|
|
233
|
+
prepare: profile.prepare,
|
|
234
|
+
...(profile.environment ? { environment: profile.environment } : {}),
|
|
235
|
+
...(profile.lifecycle ? { lifecycle: profile.lifecycle } : {}),
|
|
236
|
+
...(profile.toolchain ? { toolchain: profile.toolchain } : {}),
|
|
237
|
+
// Защищённые пути профиля дополняют умолчание политики, а не заменяют его.
|
|
238
|
+
protectedPaths: [
|
|
239
|
+
...new Set([
|
|
240
|
+
...repositoryPolicySchema.shape.protectedPaths.parse(undefined),
|
|
241
|
+
...profile.protectedPaths,
|
|
242
|
+
]),
|
|
243
|
+
],
|
|
244
|
+
generatedPaths: profile.generatedPaths,
|
|
245
|
+
id: own.id,
|
|
246
|
+
path: own.path,
|
|
247
|
+
targetRef: own.targetRef,
|
|
248
|
+
});
|
|
249
|
+
const repository = repositoryPolicySchema.parse({ ...fromProfile, ...own });
|
|
250
|
+
// Сравнение — разобранных значений: то же окружение, записанное короче, не переопределение.
|
|
251
|
+
const shadowed = profiledKeys.filter((key) => own[key] !== undefined &&
|
|
252
|
+
offered(key) &&
|
|
253
|
+
JSON.stringify(repository[key]) !== JSON.stringify(fromProfile[key]));
|
|
254
|
+
return { repository, shadowed: [...shadowed] };
|
|
255
|
+
}
|
|
256
|
+
/** Роли профиля в форме политики: writer, область записи и правило ревью кода роли. */
|
|
257
|
+
export function profileRoles(profile) {
|
|
258
|
+
return Object.fromEntries(Object.entries(profile.roles).map(([id, role]) => {
|
|
259
|
+
const { runtime, model, effort, toolProfile, writePaths, reviewer, requiresContract } = role;
|
|
260
|
+
const binding = roleBindingSchema.parse({
|
|
261
|
+
writer: {
|
|
262
|
+
runtime,
|
|
263
|
+
...(model ? { model } : {}),
|
|
264
|
+
...(effort ? { effort } : {}),
|
|
265
|
+
...(toolProfile ? { toolProfile } : {}),
|
|
266
|
+
},
|
|
267
|
+
...(writePaths ? { writePaths } : {}),
|
|
268
|
+
...(reviewer ? { review: { reviewer } } : {}),
|
|
269
|
+
...(requiresContract !== undefined ? { requiresContract } : {}),
|
|
270
|
+
});
|
|
271
|
+
return [id, binding];
|
|
272
|
+
}));
|
|
273
|
+
}
|