@bjoernrochel/sevro 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +34 -4
- package/README.md +7 -6
- package/dist/examples/basic/graded.json +13 -0
- package/dist/examples/basic/host.js +9 -0
- package/dist/examples/basic/host.js.map +1 -0
- package/dist/examples/basic/prompt-only.json +7 -0
- package/dist/examples/recipes/candidate.js +23 -0
- package/dist/examples/recipes/candidate.js.map +1 -0
- package/dist/examples/recipes/cases/coding-change.json +21 -0
- package/dist/examples/recipes/cases/host-tools.json +21 -0
- package/dist/examples/recipes/cases/mise.json +21 -0
- package/dist/examples/recipes/cases/node.json +21 -0
- package/dist/examples/recipes/cases/python-uv.json +21 -0
- package/dist/examples/recipes/cases/semantic.json +17 -0
- package/dist/examples/recipes/coding-change/check.js +16 -0
- package/dist/examples/recipes/coding-change/check.js.map +1 -0
- package/dist/examples/recipes/executable.js +20 -0
- package/dist/examples/recipes/executable.js.map +1 -0
- package/dist/examples/recipes/extension-artifacts.js +56 -0
- package/dist/examples/recipes/extension-artifacts.js.map +1 -0
- package/dist/examples/recipes/extension-methods.js +167 -0
- package/dist/examples/recipes/extension-methods.js.map +1 -0
- package/dist/examples/recipes/extension.js +28 -0
- package/dist/examples/recipes/extension.js.map +1 -0
- package/dist/examples/recipes/modules.js +9 -0
- package/dist/examples/recipes/modules.js.map +1 -0
- package/dist/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
- package/dist/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
- package/dist/examples/recipes/repository-skill/README.md +3 -0
- package/dist/examples/recipes/run-inputs.js +144 -0
- package/dist/examples/recipes/run-inputs.js.map +1 -0
- package/dist/examples/recipes/run-options.js +103 -0
- package/dist/examples/recipes/run-options.js.map +1 -0
- package/dist/examples/recipes/run.js +62 -0
- package/dist/examples/recipes/run.js.map +1 -0
- package/dist/examples/recipes/runtime/empty.json +1 -0
- package/dist/examples/recipes/runtime/goals.json +1 -0
- package/dist/examples/recipes/runtime/mise.json +6 -0
- package/dist/examples/recipes/runtime/path.json +1 -0
- package/dist/examples/recipes/runtime/plugins.json +4 -0
- package/dist/examples/recipes/runtime/python-uv.json +10 -0
- package/dist/examples/recipes/semantic-judge.js +27 -0
- package/dist/examples/recipes/semantic-judge.js.map +1 -0
- package/dist/examples/templates/coding/adapter.js +14 -0
- package/dist/examples/templates/coding/adapter.js.map +1 -0
- package/dist/examples/templates/coding/case.json +15 -0
- package/dist/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
- package/dist/examples/templates/repository-skill/extension-data.js +61 -0
- package/dist/examples/templates/repository-skill/extension-data.js.map +1 -0
- package/dist/examples/templates/repository-skill/extension.js +29 -0
- package/dist/examples/templates/repository-skill/extension.js.map +1 -0
- package/dist/examples/templates/response/adapter.js +13 -0
- package/dist/examples/templates/response/adapter.js.map +1 -0
- package/dist/examples/templates/response/case.json +13 -0
- package/dist/src/advisory.js +86 -0
- package/dist/src/advisory.js.map +1 -0
- package/dist/src/candidate-artifacts.js +139 -0
- package/dist/src/candidate-artifacts.js.map +1 -0
- package/dist/src/candidate-codex-state.js +110 -0
- package/dist/src/candidate-codex-state.js.map +1 -0
- package/dist/src/case-validation.js +92 -0
- package/dist/src/case-validation.js.map +1 -0
- package/dist/src/cli/cancellation.js +21 -0
- package/dist/src/cli/cancellation.js.map +1 -0
- package/dist/src/cli/command.js +60 -0
- package/dist/src/cli/command.js.map +1 -0
- package/dist/src/cli/completion.js +14 -0
- package/dist/src/cli/completion.js.map +1 -0
- package/dist/src/cli/demo.js +45 -0
- package/dist/src/cli/demo.js.map +1 -0
- package/dist/src/cli/evaluation.js +40 -0
- package/dist/src/cli/evaluation.js.map +1 -0
- package/dist/src/cli/extension.js +54 -0
- package/dist/src/cli/extension.js.map +1 -0
- package/dist/src/cli/help.js +190 -0
- package/dist/src/cli/help.js.map +1 -0
- package/dist/src/cli/hosts.js +33 -0
- package/dist/src/cli/hosts.js.map +1 -0
- package/dist/src/cli/init-settings.js +29 -0
- package/dist/src/cli/init-settings.js.map +1 -0
- package/dist/src/cli/init.js +82 -0
- package/dist/src/cli/init.js.map +1 -0
- package/dist/src/cli/invocation.js +431 -0
- package/dist/src/cli/invocation.js.map +1 -0
- package/dist/src/cli/loading.js +110 -0
- package/dist/src/cli/loading.js.map +1 -0
- package/dist/src/cli/preparation.js +88 -0
- package/dist/src/cli/preparation.js.map +1 -0
- package/dist/src/cli/presentation.js +61 -0
- package/dist/src/cli/presentation.js.map +1 -0
- package/dist/src/cli/progress.js +5 -0
- package/dist/src/cli/progress.js.map +1 -0
- package/dist/src/cli/run-manifest.js +108 -0
- package/dist/src/cli/run-manifest.js.map +1 -0
- package/dist/src/cli/run-options.js +367 -0
- package/dist/src/cli/run-options.js.map +1 -0
- package/dist/src/cli/run-overrides.js +51 -0
- package/dist/src/cli/run-overrides.js.map +1 -0
- package/dist/src/cli/settings.js +127 -0
- package/dist/src/cli/settings.js.map +1 -0
- package/dist/src/cli/summary.js +28 -0
- package/dist/src/cli/summary.js.map +1 -0
- package/dist/src/cli.js +20 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/contract-types.js +2 -0
- package/dist/src/contract-types.js.map +1 -0
- package/dist/src/engine.js +4 -0
- package/dist/src/engine.js.map +1 -0
- package/dist/src/evaluation/advisory.js +85 -0
- package/dist/src/evaluation/advisory.js.map +1 -0
- package/dist/src/evaluation/check-plan.js +292 -0
- package/dist/src/evaluation/check-plan.js.map +1 -0
- package/dist/src/evaluation/collection.js +61 -0
- package/dist/src/evaluation/collection.js.map +1 -0
- package/dist/src/evaluation/context.js +2 -0
- package/dist/src/evaluation/context.js.map +1 -0
- package/dist/src/evaluation/grading-host.js +28 -0
- package/dist/src/evaluation/grading-host.js.map +1 -0
- package/dist/src/evaluation/grading.js +154 -0
- package/dist/src/evaluation/grading.js.map +1 -0
- package/dist/src/evaluation/invocation-plan.js +312 -0
- package/dist/src/evaluation/invocation-plan.js.map +1 -0
- package/dist/src/evaluation/plan.js +376 -0
- package/dist/src/evaluation/plan.js.map +1 -0
- package/dist/src/evaluation/progress.js +14 -0
- package/dist/src/evaluation/progress.js.map +1 -0
- package/dist/src/evaluation/resources.js +159 -0
- package/dist/src/evaluation/resources.js.map +1 -0
- package/dist/src/evaluation/run.js +148 -0
- package/dist/src/evaluation/run.js.map +1 -0
- package/dist/src/evaluation/scheduler.js +27 -0
- package/dist/src/evaluation/scheduler.js.map +1 -0
- package/dist/src/evaluation/semantic.js +183 -0
- package/dist/src/evaluation/semantic.js.map +1 -0
- package/dist/src/evaluation/trial.js +593 -0
- package/dist/src/evaluation/trial.js.map +1 -0
- package/dist/src/evaluation/types.js +20 -0
- package/dist/src/evaluation/types.js.map +1 -0
- package/dist/src/evidence/host-results.js +67 -0
- package/dist/src/evidence/host-results.js.map +1 -0
- package/dist/src/evidence/retained-artifacts.js +17 -0
- package/dist/src/evidence/retained-artifacts.js.map +1 -0
- package/dist/src/evidence/trial-writer.js +131 -0
- package/dist/src/evidence/trial-writer.js.map +1 -0
- package/dist/src/evidence/usage.js +14 -0
- package/dist/src/evidence/usage.js.map +1 -0
- package/dist/src/executable.js +20 -0
- package/dist/src/executable.js.map +1 -0
- package/dist/src/extension-client.js +212 -0
- package/dist/src/extension-client.js.map +1 -0
- package/dist/src/extension-session.js +236 -0
- package/dist/src/extension-session.js.map +1 -0
- package/dist/src/fixtures/advisory.js +206 -0
- package/dist/src/fixtures/advisory.js.map +1 -0
- package/dist/src/fixtures/bin.js +41 -0
- package/dist/src/fixtures/bin.js.map +1 -0
- package/dist/src/fixtures/cleanup.js +43 -0
- package/dist/src/fixtures/cleanup.js.map +1 -0
- package/dist/src/fixtures/create.js +94 -0
- package/dist/src/fixtures/create.js.map +1 -0
- package/dist/src/fixtures/generated.js +181 -0
- package/dist/src/fixtures/generated.js.map +1 -0
- package/dist/src/fixtures/git-hooks.js +16 -0
- package/dist/src/fixtures/git-hooks.js.map +1 -0
- package/dist/src/fixtures/preparation.js +201 -0
- package/dist/src/fixtures/preparation.js.map +1 -0
- package/dist/src/fixtures/repository.js +255 -0
- package/dist/src/fixtures/repository.js.map +1 -0
- package/dist/src/fixtures/setup.js +175 -0
- package/dist/src/fixtures/setup.js.map +1 -0
- package/dist/src/fixtures/tools.js +57 -0
- package/dist/src/fixtures/tools.js.map +1 -0
- package/dist/src/fixtures/workspace-state.js +155 -0
- package/dist/src/fixtures/workspace-state.js.map +1 -0
- package/dist/src/generated/cli-result.cjs +2 -0
- package/dist/src/generated/extension.cjs +2 -0
- package/dist/src/generated/report.cjs +2 -0
- package/dist/src/generated/run-evidence.cjs +2 -0
- package/dist/src/generated/runtime.cjs +2 -0
- package/dist/src/git.js +72 -0
- package/dist/src/git.js.map +1 -0
- package/dist/src/graders/artifact.js +68 -0
- package/dist/src/graders/artifact.js.map +1 -0
- package/dist/src/graders/git-head.js +112 -0
- package/dist/src/graders/git-head.js.map +1 -0
- package/dist/src/graders/output.js +345 -0
- package/dist/src/graders/output.js.map +1 -0
- package/dist/src/graders/semantic.js +132 -0
- package/dist/src/graders/semantic.js.map +1 -0
- package/dist/src/graders/shell.js +321 -0
- package/dist/src/graders/shell.js.map +1 -0
- package/dist/src/host-diagnostic-redaction.js +52 -0
- package/dist/src/host-diagnostic-redaction.js.map +1 -0
- package/dist/src/host-diagnostics.js +248 -0
- package/dist/src/host-diagnostics.js.map +1 -0
- package/dist/src/hosts/claude-continuation.js +89 -0
- package/dist/src/hosts/claude-continuation.js.map +1 -0
- package/dist/src/hosts/claude-credential.js +121 -0
- package/dist/src/hosts/claude-credential.js.map +1 -0
- package/dist/src/hosts/claude-events.js +136 -0
- package/dist/src/hosts/claude-events.js.map +1 -0
- package/dist/src/hosts/claude-keychain.js +100 -0
- package/dist/src/hosts/claude-keychain.js.map +1 -0
- package/dist/src/hosts/claude-login-recovery.js +67 -0
- package/dist/src/hosts/claude-login-recovery.js.map +1 -0
- package/dist/src/hosts/claude-login-refresh.js +146 -0
- package/dist/src/hosts/claude-login-refresh.js.map +1 -0
- package/dist/src/hosts/claude-login-store.js +124 -0
- package/dist/src/hosts/claude-login-store.js.map +1 -0
- package/dist/src/hosts/claude-login.js +74 -0
- package/dist/src/hosts/claude-login.js.map +1 -0
- package/dist/src/hosts/claude-native-goal.js +138 -0
- package/dist/src/hosts/claude-native-goal.js.map +1 -0
- package/dist/src/hosts/claude-process.js +10 -0
- package/dist/src/hosts/claude-process.js.map +1 -0
- package/dist/src/hosts/claude-refresh-lock.js +76 -0
- package/dist/src/hosts/claude-refresh-lock.js.map +1 -0
- package/dist/src/hosts/claude-repository-invocation.js +229 -0
- package/dist/src/hosts/claude-repository-invocation.js.map +1 -0
- package/dist/src/hosts/claude-settings.js +156 -0
- package/dist/src/hosts/claude-settings.js.map +1 -0
- package/dist/src/hosts/claude-skill-activations.js +131 -0
- package/dist/src/hosts/claude-skill-activations.js.map +1 -0
- package/dist/src/hosts/claude-state.js +237 -0
- package/dist/src/hosts/claude-state.js.map +1 -0
- package/dist/src/hosts/claude-tool-calls.js +150 -0
- package/dist/src/hosts/claude-tool-calls.js.map +1 -0
- package/dist/src/hosts/claude.js +373 -0
- package/dist/src/hosts/claude.js.map +1 -0
- package/dist/src/hosts/codex-app-server.js +764 -0
- package/dist/src/hosts/codex-app-server.js.map +1 -0
- package/dist/src/hosts/codex-configuration.js +91 -0
- package/dist/src/hosts/codex-configuration.js.map +1 -0
- package/dist/src/hosts/codex-events.js +104 -0
- package/dist/src/hosts/codex-events.js.map +1 -0
- package/dist/src/hosts/codex-exec-controls.js +434 -0
- package/dist/src/hosts/codex-exec-controls.js.map +1 -0
- package/dist/src/hosts/codex-invocation.js +54 -0
- package/dist/src/hosts/codex-invocation.js.map +1 -0
- package/dist/src/hosts/codex-marketplace.js +185 -0
- package/dist/src/hosts/codex-marketplace.js.map +1 -0
- package/dist/src/hosts/codex-native-calls.js +516 -0
- package/dist/src/hosts/codex-native-calls.js.map +1 -0
- package/dist/src/hosts/codex-process.js +10 -0
- package/dist/src/hosts/codex-process.js.map +1 -0
- package/dist/src/hosts/codex-profile.js +206 -0
- package/dist/src/hosts/codex-profile.js.map +1 -0
- package/dist/src/hosts/codex-session-files.js +89 -0
- package/dist/src/hosts/codex-session-files.js.map +1 -0
- package/dist/src/hosts/codex-state.js +310 -0
- package/dist/src/hosts/codex-state.js.map +1 -0
- package/dist/src/hosts/codex.js +527 -0
- package/dist/src/hosts/codex.js.map +1 -0
- package/dist/src/hosts/isolation-common.js +43 -0
- package/dist/src/hosts/isolation-common.js.map +1 -0
- package/dist/src/hosts/isolation-roots.js +64 -0
- package/dist/src/hosts/isolation-roots.js.map +1 -0
- package/dist/src/hosts/isolation.js +12 -0
- package/dist/src/hosts/isolation.js.map +1 -0
- package/dist/src/hosts/linux-sandbox.js +127 -0
- package/dist/src/hosts/linux-sandbox.js.map +1 -0
- package/dist/src/hosts/mac-sandbox.js +137 -0
- package/dist/src/hosts/mac-sandbox.js.map +1 -0
- package/dist/src/hosts/native-controls.js +151 -0
- package/dist/src/hosts/native-controls.js.map +1 -0
- package/dist/src/hosts/native-request.js +33 -0
- package/dist/src/hosts/native-request.js.map +1 -0
- package/dist/src/hosts/process-lifecycle.js +167 -0
- package/dist/src/hosts/process-lifecycle.js.map +1 -0
- package/dist/src/hosts/protected-worktrees.js +81 -0
- package/dist/src/hosts/protected-worktrees.js.map +1 -0
- package/dist/src/hosts/skill-hook-catalog.js +100 -0
- package/dist/src/hosts/skill-hook-catalog.js.map +1 -0
- package/dist/src/hosts/skill-hook-handler.js +228 -0
- package/dist/src/hosts/skill-hook-handler.js.map +1 -0
- package/dist/src/hosts/skill-hooks.js +423 -0
- package/dist/src/hosts/skill-hooks.js.map +1 -0
- package/dist/src/hosts/workspace-fingerprint.js +72 -0
- package/dist/src/hosts/workspace-fingerprint.js.map +1 -0
- package/dist/src/identity.js +167 -0
- package/dist/src/identity.js.map +1 -0
- package/dist/src/instrumentation.js +83 -0
- package/dist/src/instrumentation.js.map +1 -0
- package/dist/src/native-transcript-state.js +85 -0
- package/dist/src/native-transcript-state.js.map +1 -0
- package/dist/src/native-transcript-view.js +65 -0
- package/dist/src/native-transcript-view.js.map +1 -0
- package/dist/src/native-transcripts.js +208 -0
- package/dist/src/native-transcripts.js.map +1 -0
- package/dist/src/paths.js +13 -0
- package/dist/src/paths.js.map +1 -0
- package/dist/src/process-lifecycle.js +101 -0
- package/dist/src/process-lifecycle.js.map +1 -0
- package/dist/src/provenance.js +263 -0
- package/dist/src/provenance.js.map +1 -0
- package/dist/src/prune.js +228 -0
- package/dist/src/prune.js.map +1 -0
- package/dist/src/report.js +276 -0
- package/dist/src/report.js.map +1 -0
- package/dist/src/resolved-case.js +29 -0
- package/dist/src/resolved-case.js.map +1 -0
- package/dist/src/results-lock.js +40 -0
- package/dist/src/results-lock.js.map +1 -0
- package/dist/src/results.js +127 -0
- package/dist/src/results.js.map +1 -0
- package/dist/src/run-owner.js +283 -0
- package/dist/src/run-owner.js.map +1 -0
- package/dist/src/runner-paths.js +11 -0
- package/dist/src/runner-paths.js.map +1 -0
- package/dist/src/runtime/config.js +323 -0
- package/dist/src/runtime/config.js.map +1 -0
- package/dist/src/runtime/discovery-apple.js +65 -0
- package/dist/src/runtime/discovery-apple.js.map +1 -0
- package/dist/src/runtime/discovery-providers.js +37 -0
- package/dist/src/runtime/discovery-providers.js.map +1 -0
- package/dist/src/runtime/discovery.js +40 -0
- package/dist/src/runtime/discovery.js.map +1 -0
- package/dist/src/runtime/hook-processes.js +81 -0
- package/dist/src/runtime/hook-processes.js.map +1 -0
- package/dist/src/runtime/hooks.js +304 -0
- package/dist/src/runtime/hooks.js.map +1 -0
- package/dist/src/runtime/paths.js +244 -0
- package/dist/src/runtime/paths.js.map +1 -0
- package/dist/src/runtime/query.js +76 -0
- package/dist/src/runtime/query.js.map +1 -0
- package/dist/src/runtime/seeds.js +98 -0
- package/dist/src/runtime/seeds.js.map +1 -0
- package/dist/src/runtime/shared-cache.js +81 -0
- package/dist/src/runtime/shared-cache.js.map +1 -0
- package/dist/src/runtime/state.js +106 -0
- package/dist/src/runtime/state.js.map +1 -0
- package/dist/src/runtime-process.js +148 -0
- package/dist/src/runtime-process.js.map +1 -0
- package/dist/src/schema-types.js +2 -0
- package/dist/src/schema-types.js.map +1 -0
- package/dist/src/schema.js +16 -0
- package/dist/src/schema.js.map +1 -0
- package/dist/src/shell.js +5 -0
- package/dist/src/shell.js.map +1 -0
- package/dist/src/sqlite-lock.js +18 -0
- package/dist/src/sqlite-lock.js.map +1 -0
- package/dist/src/storage.js +21 -0
- package/dist/src/storage.js.map +1 -0
- package/dist/src/transcript-retention.js +109 -0
- package/dist/src/transcript-retention.js.map +1 -0
- package/dist/src/value-guards.js +26 -0
- package/dist/src/value-guards.js.map +1 -0
- package/docs/architecture.md +5 -2
- package/docs/creating-cases.md +2 -3
- package/docs/development-cli.md +12 -7
- package/docs/documentation-quality.md +4 -3
- package/docs/evaluation-workflow.md +8 -8
- package/docs/getting-started.md +7 -6
- package/docs/guide-evaluation.md +1 -1
- package/docs/identity-v1.md +4 -1
- package/docs/installing.md +31 -13
- package/docs/licensing.md +2 -2
- package/docs/native-hosts.md +1 -1
- package/docs/recipes.md +41 -40
- package/docs/releases.md +20 -4
- package/docs/run-settings-v1.md +3 -3
- package/docs/specs/repository-guide.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/typescript-quality.md +18 -1
- package/examples/recipes/executable.ts +19 -0
- package/examples/recipes/extension-artifacts.ts +1 -1
- package/examples/recipes/extension.ts +2 -1
- package/examples/recipes/modules.ts +9 -0
- package/examples/recipes/run-inputs.ts +14 -19
- package/examples/recipes/run-options.ts +9 -8
- package/examples/recipes/run.ts +21 -7
- package/examples/templates/repository-skill/extension-data.ts +1 -1
- package/examples/templates/repository-skill/extension.ts +2 -1
- package/package.json +14 -6
- package/src/cli/demo.ts +3 -2
- package/src/cli/help.ts +4 -3
- package/src/cli/init-settings.ts +5 -4
- package/src/cli/init.ts +9 -8
- package/src/cli/invocation.ts +2 -1
- package/src/cli/preparation.ts +2 -2
- package/src/cli.ts +1 -1
- package/src/executable.ts +19 -0
- package/src/extension-client.ts +6 -12
- package/src/fixtures/setup.ts +3 -2
- package/src/git.ts +2 -1
- package/src/graders/git-head.ts +3 -2
- package/src/graders/shell.ts +4 -3
- package/src/hosts/claude-keychain.ts +3 -2
- package/src/hosts/claude-login-refresh.ts +3 -2
- package/src/hosts/claude-refresh-lock.ts +2 -1
- package/src/hosts/isolation-roots.ts +3 -2
- package/src/hosts/process-lifecycle.ts +4 -6
- package/src/hosts/protected-worktrees.ts +2 -1
- package/src/hosts/skill-hook-handler.ts +1 -1
- package/src/hosts/skill-hooks.ts +10 -2
- package/src/provenance.ts +7 -6
- package/src/results-lock.ts +4 -4
- package/src/run-owner.ts +15 -9
- package/src/runner-paths.ts +21 -0
- package/src/runtime/hook-processes.ts +2 -1
- package/src/runtime/hooks.ts +9 -7
- package/src/runtime-process.ts +194 -0
- package/src/sqlite-lock.ts +25 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generated.js","sourceRoot":"","sources":["generated.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { canonicalJson } from \"../identity\";\nimport { prepareFixtureBin } from \"./bin\";\nimport { prepareGitHooks } from \"./git-hooks\";\nimport { fixtureParts } from \"./preparation\";\nimport { runGit } from \"../git\";\n\nconst MAX_COMMITS = 128;\nconst MAX_FILES = 1024;\nconst MAX_BYTES = 32 * 1024 * 1024;\nconst MAX_GIT_OUTPUT = 64 * 1024;\n\nexport interface GeneratedFixture {\n kind: \"generated\";\n commits: { message: string; files: Record<string, string> }[];\n files?: Record<string, string>;\n staged?: string[];\n commitFiles?: boolean;\n hooks?: Record<string, string>;\n bin?: Record<string, string>;\n}\n\nfunction validFiles(value: unknown): value is Record<string, string> {\n return (\n Boolean(value && typeof value === \"object\" && !Array.isArray(value)) &&\n Object.values(value as object).every((item) => typeof item === \"string\")\n );\n}\n\nfunction validPath(path: string): void {\n const parts = fixtureParts(path);\n if (parts.some((part) => part.toLowerCase() === \".git\"))\n throw new Error(\"generated fixture cannot write repository metadata\");\n}\n\ninterface FixtureContents {\n bytes: number;\n files: number;\n paths: string[];\n}\n\nfunction fixtureRecord(value: unknown): Record<string, unknown> {\n if (!value || typeof value !== \"object\" || Array.isArray(value))\n throw new Error(\"invalid generated fixture\");\n return value as Record<string, unknown>;\n}\n\nfunction fixtureKeys(fixture: Record<string, unknown>): void {\n const keys = [\n \"kind\",\n \"commits\",\n \"files\",\n \"staged\",\n \"commitFiles\",\n \"hooks\",\n \"bin\",\n ];\n if (\n fixture.kind !== \"generated\" ||\n Object.keys(fixture).some((key) => !keys.includes(key))\n )\n throw new Error(\"invalid generated fixture\");\n}\n\nfunction stringArray(value: unknown): value is string[] {\n return (\n Array.isArray(value) &&\n value.every((item: unknown) => typeof item === \"string\")\n );\n}\n\nfunction fixtureOptionalFields(fixture: Record<string, unknown>): void {\n if (fixture.files !== undefined && !validFiles(fixture.files))\n throw new Error(\"invalid generated fixture\");\n validateStagedField(fixture.staged);\n if (\n fixture.commitFiles !== undefined &&\n typeof fixture.commitFiles !== \"boolean\"\n )\n throw new Error(\"invalid generated fixture\");\n}\n\nfunction validateStagedField(value: unknown): void {\n if (value !== undefined && !stringArray(value))\n throw new Error(\"invalid generated fixture\");\n}\n\nfunction fixtureCommits(value: unknown): unknown[] {\n if (!Array.isArray(value) || value.length > MAX_COMMITS)\n throw new Error(\"invalid generated fixture\");\n return value;\n}\n\nfunction commitMessage(value: unknown): value is string {\n return (\n typeof value === \"string\" &&\n Boolean(value.trim()) &&\n Buffer.byteLength(value, \"utf8\") <= 4096\n );\n}\n\nfunction commitRecord(value: unknown): Record<string, unknown> {\n if (!value || typeof value !== \"object\" || Array.isArray(value))\n throw new Error(\"invalid generated fixture commit\");\n return value as Record<string, unknown>;\n}\n\nfunction prepareCommit(value: unknown): GeneratedFixture[\"commits\"][number] {\n const commit = commitRecord(value);\n if (\n Object.keys(commit).some((key) => key !== \"message\" && key !== \"files\") ||\n !commitMessage(commit.message)\n )\n throw new Error(\"invalid generated fixture commit\");\n if (!validFiles(commit.files) || Object.keys(commit.files).length === 0)\n throw new Error(\"invalid generated fixture commit\");\n return { message: commit.message, files: commit.files };\n}\n\nfunction countFiles(\n contents: FixtureContents,\n files: Record<string, string>,\n): void {\n for (const [path, content] of Object.entries(files)) {\n validPath(path);\n contents.paths.push(path);\n contents.files++;\n contents.bytes +=\n Buffer.byteLength(path, \"utf8\") + Buffer.byteLength(content, \"utf8\");\n }\n}\n\nfunction validateContents(contents: FixtureContents): void {\n if (contents.files > MAX_FILES || contents.bytes > MAX_BYTES)\n throw new Error(\"generated fixture exceeds the size limit\");\n const normalized = [...new Set(contents.paths)].map((path) =>\n path.toLowerCase(),\n );\n for (const [index, path] of normalized.entries()) {\n if (normalized.slice(0, index).some((other) => pathsCollide(path, other)))\n throw new Error(\"generated fixture paths collide\");\n }\n}\n\nfunction pathsCollide(path: string, other: string): boolean {\n return (\n path === other ||\n path.startsWith(`${other}/`) ||\n other.startsWith(`${path}/`)\n );\n}\n\nfunction validateStaging(\n staged: string[],\n overlay: Record<string, string>,\n commitFiles: unknown,\n): void {\n if (\n new Set(staged.map((path) => path.toLowerCase())).size !== staged.length ||\n staged.some((path) => !Object.hasOwn(overlay, path)) ||\n emptyOverlayCommit(commitFiles, overlay)\n )\n throw new Error(\"invalid generated fixture staging\");\n}\n\nfunction emptyOverlayCommit(\n commitFiles: unknown,\n overlay: Record<string, string>,\n): boolean {\n return commitFiles === true && Object.keys(overlay).length === 0;\n}\n\n/** Freeze a bounded declarative history before any host or fixture work. */\nexport function prepareGeneratedFixture(value: unknown): GeneratedFixture {\n const fixture = fixtureRecord(value);\n fixtureKeys(fixture);\n const commits = fixtureCommits(fixture.commits);\n fixtureOptionalFields(fixture);\n prepareGitHooks(fixture.hooks);\n prepareFixtureBin(fixture.bin);\n const contents: FixtureContents = { bytes: 0, files: 0, paths: [] };\n for (const value of commits) {\n const commit = prepareCommit(value);\n contents.bytes += Buffer.byteLength(commit.message, \"utf8\");\n countFiles(contents, commit.files);\n }\n // The optional fields were checked above; preserve absent fields in the frozen declaration.\n const overlay = validFiles(fixture.files) ? fixture.files : {};\n const staged = stringArray(fixture.staged) ? fixture.staged : [];\n countFiles(contents, overlay);\n contents.bytes += staged.reduce(\n (size, path) => size + Buffer.byteLength(path, \"utf8\"),\n 0,\n );\n validateContents(contents);\n validateStaging(staged, overlay, fixture.commitFiles);\n return JSON.parse(canonicalJson(fixture)) as GeneratedFixture;\n}\n\nasync function git(workspace: string, args: string[]): Promise<void> {\n try {\n await runGit({\n cwd: workspace,\n args,\n identity: \"fixture\",\n label: \"generated fixture\",\n maxOutputBytes: MAX_GIT_OUTPUT,\n });\n } catch (cause) {\n throw new Error(\"generated fixture Git operation failed\", { cause });\n }\n}\n\nasync function writeFiles(\n workspace: string,\n files: Record<string, string>,\n): Promise<void> {\n for (const [path, content] of Object.entries(files)) {\n const target = join(workspace, ...fixtureParts(path));\n await mkdir(dirname(target), { recursive: true, mode: 0o700 });\n await writeFile(target, content, { flag: \"w\", mode: 0o600 });\n }\n}\n\n/** Build a deterministic local Git history, then apply working-tree state. */\nexport async function materializeGeneratedFixture(\n fixture: GeneratedFixture,\n workspace: string,\n): Promise<void> {\n await git(workspace, [\"init\", \"--quiet\", \"--initial-branch=main\"]);\n for (const commit of fixture.commits) {\n await writeFiles(workspace, commit.files);\n await git(workspace, [\"add\", \"--\", ...Object.keys(commit.files)]);\n await git(workspace, [\"commit\", \"--quiet\", \"-m\", commit.message]);\n }\n await applyGeneratedOverlay(fixture, workspace);\n}\n\nasync function applyGeneratedOverlay(\n fixture: GeneratedFixture,\n workspace: string,\n): Promise<void> {\n const overlay = fixture.files ?? {};\n await writeFiles(workspace, overlay);\n if (fixture.commitFiles) {\n await git(workspace, [\"add\", \"--\", ...Object.keys(overlay)]);\n await git(workspace, [\n \"commit\",\n \"--quiet\",\n \"-m\",\n \"Add evaluation scaffolding\",\n ]);\n }\n if (fixture.staged?.length)\n await git(workspace, [\"add\", \"--\", ...fixture.staged]);\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { prepareFixtureTools, installFixtureTools } from "./tools.js";
|
|
3
|
+
/** Bound hook declarations and keep every name inside Git's hooks directory. */
|
|
4
|
+
export function prepareGitHooks(value) {
|
|
5
|
+
return prepareFixtureTools(value, /^[a-z][a-z0-9-]*$/, 32, "invalid fixture hooks");
|
|
6
|
+
}
|
|
7
|
+
/** Install executable hooks only after fixture commits and overlays are ready. */
|
|
8
|
+
export async function installGitHooks(hooks, workspace) {
|
|
9
|
+
if (!hooks)
|
|
10
|
+
return;
|
|
11
|
+
await installFixtureTools(hooks, join(workspace, ".git", "hooks"), {
|
|
12
|
+
directory: "fixture Git hooks directory is invalid",
|
|
13
|
+
target: "fixture Git hook target is invalid",
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=git-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-hooks.js","sourceRoot":"","sources":["git-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,kBAAkB;AACjC,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,mBAAgB;AAEnE,gFAAgF;AAChF,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,OAAO,mBAAmB,CACxB,KAAK,EACL,mBAAmB,EACnB,EAAE,EACF,uBAAuB,CACxB,CAAC;AACJ,CAAC;AAED,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,KAAyC,EACzC,SAAiB;IAEjB,IAAI,CAAC,KAAK;QAAE,OAAO;IACnB,MAAM,mBAAmB,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QACjE,SAAS,EAAE,wCAAwC;QACnD,MAAM,EAAE,oCAAoC;KAC7C,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { join } from \"node:path\";\nimport { prepareFixtureTools, installFixtureTools } from \"./tools\";\n\n/** Bound hook declarations and keep every name inside Git's hooks directory. */\nexport function prepareGitHooks(value: unknown): Record<string, string> | null {\n return prepareFixtureTools(\n value,\n /^[a-z][a-z0-9-]*$/,\n 32,\n \"invalid fixture hooks\",\n );\n}\n\n/** Install executable hooks only after fixture commits and overlays are ready. */\nexport async function installGitHooks(\n hooks: Record<string, string> | undefined,\n workspace: string,\n): Promise<void> {\n if (!hooks) return;\n await installFixtureTools(hooks, join(workspace, \".git\", \"hooks\"), {\n directory: \"fixture Git hooks directory is invalid\",\n target: \"fixture Git hook target is invalid\",\n });\n}\n"]}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { isMissingFile } from "./tools.js";
|
|
2
|
+
import { sha256 } from "../identity.js";
|
|
3
|
+
import { lstat, mkdir, readFile, realpath, stat } from "node:fs/promises";
|
|
4
|
+
import { isAbsolute, join } from "node:path";
|
|
5
|
+
import { insideRoot } from "../paths.js";
|
|
6
|
+
import { containsControl as containsControlCharacter } from "../value-guards.js";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
const MAX_ARTIFACT_BYTES = 8 * 1024 * 1024;
|
|
9
|
+
const MAX_TOTAL_BYTES = 32 * 1024 * 1024;
|
|
10
|
+
export function fixtureParts(relativePath) {
|
|
11
|
+
if (!validFixturePath(relativePath))
|
|
12
|
+
throw new Error(`invalid fixture path: ${relativePath}`);
|
|
13
|
+
const parts = relativePath.split("/");
|
|
14
|
+
if (parts.some((part) => !part || part === "." || part === ".."))
|
|
15
|
+
throw new Error(`invalid fixture path: ${relativePath}`);
|
|
16
|
+
return parts;
|
|
17
|
+
}
|
|
18
|
+
function validFixturePath(path) {
|
|
19
|
+
return !!path && !isAbsolute(path) && !path.includes("\\") && !/:/.test(path);
|
|
20
|
+
}
|
|
21
|
+
/** Refuse fixture or setup-created links before mounting trusted artifact bytes. */
|
|
22
|
+
export async function safePreparationTarget(workspace, relativePath) {
|
|
23
|
+
const parts = fixtureParts(relativePath);
|
|
24
|
+
let parent = workspace;
|
|
25
|
+
for (const part of parts.slice(0, -1)) {
|
|
26
|
+
parent = join(parent, part);
|
|
27
|
+
await preparationParent(parent);
|
|
28
|
+
}
|
|
29
|
+
const target = join(workspace, ...parts);
|
|
30
|
+
await requireUnusedPreparationTarget(target);
|
|
31
|
+
return target;
|
|
32
|
+
}
|
|
33
|
+
async function preparationParent(path) {
|
|
34
|
+
try {
|
|
35
|
+
const entry = await lstat(path);
|
|
36
|
+
if (!entry.isDirectory() || entry.isSymbolicLink())
|
|
37
|
+
throw new Error("preparation artifact traverses a non-directory");
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (!isMissingFile(error))
|
|
41
|
+
throw error;
|
|
42
|
+
await mkdir(path, { mode: 0o700 });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function requireUnusedPreparationTarget(path) {
|
|
46
|
+
try {
|
|
47
|
+
await lstat(path);
|
|
48
|
+
throw new Error("preparation artifact path already exists");
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (!isMissingFile(error))
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function collidingPaths(left, right) {
|
|
56
|
+
return (left === right ||
|
|
57
|
+
left.startsWith(`${right}/`) ||
|
|
58
|
+
right.startsWith(`${left}/`));
|
|
59
|
+
}
|
|
60
|
+
function validatePaths(paths) {
|
|
61
|
+
const normalized = paths.map((path) => fixtureParts(path).join("/").toLowerCase());
|
|
62
|
+
for (const [index, path] of normalized.entries())
|
|
63
|
+
for (const other of normalized.slice(0, index))
|
|
64
|
+
if (collidingPaths(path, other))
|
|
65
|
+
throw new Error("fixture paths collide");
|
|
66
|
+
}
|
|
67
|
+
/** Decode declared inline artifacts before creating a candidate workspace. */
|
|
68
|
+
export function prepareInlineArtifacts(declarations, fixturePaths) {
|
|
69
|
+
validatePaths([
|
|
70
|
+
...fixturePaths,
|
|
71
|
+
...declarations.map((item) => item.relativePath),
|
|
72
|
+
]);
|
|
73
|
+
const state = { ids: new Set(), bytes: 0 };
|
|
74
|
+
return declarations.map((item) => inlineArtifact(item, state));
|
|
75
|
+
}
|
|
76
|
+
function requireArtifactFlags(item) {
|
|
77
|
+
if (item.gitExclude !== undefined && typeof item.gitExclude !== "boolean")
|
|
78
|
+
throw new Error("preparation artifact gitExclude must be boolean");
|
|
79
|
+
if (item.executable !== undefined && typeof item.executable !== "boolean")
|
|
80
|
+
throw new Error("preparation artifact executable must be boolean");
|
|
81
|
+
}
|
|
82
|
+
function requireInlineSource(item) {
|
|
83
|
+
if (item.gitExclude && containsControlCharacter(item.relativePath))
|
|
84
|
+
throw new Error("Git-excluded artifact path contains control characters");
|
|
85
|
+
if (item.sourceRef !== undefined || typeof item.contentBase64 !== "string")
|
|
86
|
+
throw new Error("preparation source references are not supported");
|
|
87
|
+
}
|
|
88
|
+
function canonicalBase64Bytes(encoded) {
|
|
89
|
+
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(encoded))
|
|
90
|
+
throw new Error("preparation artifact content is not canonical base64");
|
|
91
|
+
const bytes = Buffer.from(encoded, "base64");
|
|
92
|
+
if (bytes.toString("base64") !== encoded)
|
|
93
|
+
throw new Error("preparation artifact content is not canonical base64");
|
|
94
|
+
return bytes;
|
|
95
|
+
}
|
|
96
|
+
function requireArtifactSize(bytes, total) {
|
|
97
|
+
if (bytes > MAX_ARTIFACT_BYTES || total > MAX_TOTAL_BYTES)
|
|
98
|
+
throw new Error("preparation artifacts exceed the size limit");
|
|
99
|
+
}
|
|
100
|
+
function artifactFlags(item) {
|
|
101
|
+
return {
|
|
102
|
+
...(item.gitExclude === undefined ? {} : { gitExclude: item.gitExclude }),
|
|
103
|
+
...(item.executable === undefined ? {} : { executable: item.executable }),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function inlineArtifact(item, state) {
|
|
107
|
+
if (!item.id || state.ids.has(item.id))
|
|
108
|
+
throw new Error("preparation artifact IDs must be unique and nonempty");
|
|
109
|
+
state.ids.add(item.id);
|
|
110
|
+
requireArtifactFlags(item);
|
|
111
|
+
requireInlineSource(item);
|
|
112
|
+
const bytes = canonicalBase64Bytes(item.contentBase64);
|
|
113
|
+
state.bytes += bytes.byteLength;
|
|
114
|
+
requireArtifactSize(bytes.byteLength, state.bytes);
|
|
115
|
+
if (sha256(bytes) !== item.sha256)
|
|
116
|
+
throw new Error("preparation artifact digest does not match content");
|
|
117
|
+
return {
|
|
118
|
+
id: item.id,
|
|
119
|
+
relativePath: item.relativePath,
|
|
120
|
+
sha256: item.sha256,
|
|
121
|
+
bytes,
|
|
122
|
+
...artifactFlags(item),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/** Resolve opaque source IDs only through an explicitly declared case source map. */
|
|
126
|
+
export async function prepareArtifacts(declarations, fixturePaths, sources) {
|
|
127
|
+
if (!declarations.some((item) => item.sourceRef !== undefined))
|
|
128
|
+
return prepareInlineArtifacts(declarations, fixturePaths);
|
|
129
|
+
const context = await preparationSourceContext(sources);
|
|
130
|
+
const resolved = [];
|
|
131
|
+
const state = { bytes: 0 };
|
|
132
|
+
for (const item of declarations)
|
|
133
|
+
resolved.push(await resolveSourceArtifact(item, context, state));
|
|
134
|
+
return prepareInlineArtifacts(resolved, fixturePaths);
|
|
135
|
+
}
|
|
136
|
+
async function preparationSourceContext(sources) {
|
|
137
|
+
if (!sources || !isAbsolute(sources.root))
|
|
138
|
+
throw new Error("preparation source root is required and must be absolute");
|
|
139
|
+
const root = await realpath(sources.root).catch((cause) => {
|
|
140
|
+
throw new Error("preparation source root is unreadable", { cause });
|
|
141
|
+
});
|
|
142
|
+
return { root, refs: sources.refs };
|
|
143
|
+
}
|
|
144
|
+
function sourceArtifactUrl(item, refs) {
|
|
145
|
+
if (item.contentBase64 !== undefined)
|
|
146
|
+
throw new Error("preparation artifact has two content sources");
|
|
147
|
+
if (!Object.hasOwn(refs, item.sourceRef))
|
|
148
|
+
throw new Error("preparation source reference is not declared");
|
|
149
|
+
const url = refs[item.sourceRef];
|
|
150
|
+
if (typeof url !== "string" || !url.startsWith("file:///"))
|
|
151
|
+
throw new Error("preparation source reference must be a file URL");
|
|
152
|
+
return url;
|
|
153
|
+
}
|
|
154
|
+
async function preparationSourcePath(url) {
|
|
155
|
+
try {
|
|
156
|
+
return await realpath(fileURLToPath(url));
|
|
157
|
+
}
|
|
158
|
+
catch (cause) {
|
|
159
|
+
throw new Error("preparation source is unreadable", { cause });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function requireContainedSource(root, path) {
|
|
163
|
+
if (root === path || !insideRoot(root, path))
|
|
164
|
+
throw new Error("preparation source escapes its declared root");
|
|
165
|
+
}
|
|
166
|
+
async function preparationSourceSize(path) {
|
|
167
|
+
try {
|
|
168
|
+
return (await stat(path)).size;
|
|
169
|
+
}
|
|
170
|
+
catch (cause) {
|
|
171
|
+
throw new Error("preparation source is unreadable", { cause });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function preparationSourceBytes(path) {
|
|
175
|
+
try {
|
|
176
|
+
return await readFile(path);
|
|
177
|
+
}
|
|
178
|
+
catch (cause) {
|
|
179
|
+
throw new Error("preparation source is unreadable", { cause });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function resolveSourceArtifact(item, context, state) {
|
|
183
|
+
if (item.sourceRef === undefined)
|
|
184
|
+
return item;
|
|
185
|
+
const url = sourceArtifactUrl({ ...item, sourceRef: item.sourceRef }, context.refs);
|
|
186
|
+
const path = await preparationSourcePath(url);
|
|
187
|
+
requireContainedSource(context.root, path);
|
|
188
|
+
const size = await preparationSourceSize(path);
|
|
189
|
+
requireArtifactSize(size, state.bytes + size);
|
|
190
|
+
const bytes = await preparationSourceBytes(path);
|
|
191
|
+
state.bytes += bytes.byteLength;
|
|
192
|
+
requireArtifactSize(bytes.byteLength, state.bytes);
|
|
193
|
+
return {
|
|
194
|
+
id: item.id,
|
|
195
|
+
relativePath: item.relativePath,
|
|
196
|
+
sha256: item.sha256,
|
|
197
|
+
contentBase64: bytes.toString("base64"),
|
|
198
|
+
...artifactFlags(item),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=preparation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preparation.js","sourceRoot":"","sources":["preparation.ts"],"names":[],"mappings":"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","sourcesContent":["import { isMissingFile } from \"./tools\";\nimport { sha256 } from \"../identity\";\nimport { lstat, mkdir, readFile, realpath, stat } from \"node:fs/promises\";\nimport { isAbsolute, join } from \"node:path\";\nimport { insideRoot } from \"../paths\";\nimport { containsControl as containsControlCharacter } from \"../value-guards\";\nimport { fileURLToPath } from \"node:url\";\n\nconst MAX_ARTIFACT_BYTES = 8 * 1024 * 1024;\nconst MAX_TOTAL_BYTES = 32 * 1024 * 1024;\n\nexport interface PreparationArtifact {\n id: string;\n relativePath: string;\n sha256: string;\n contentBase64?: string;\n sourceRef?: string;\n gitExclude?: boolean;\n executable?: boolean;\n}\n\nexport interface InlineArtifact {\n id: string;\n relativePath: string;\n sha256: string;\n bytes: Uint8Array;\n gitExclude?: boolean;\n executable?: boolean;\n}\n\nexport interface PreparationSources {\n root: string;\n refs: Record<string, string>;\n}\n\nexport function fixtureParts(relativePath: string): string[] {\n if (!validFixturePath(relativePath))\n throw new Error(`invalid fixture path: ${relativePath}`);\n const parts = relativePath.split(\"/\");\n if (parts.some((part) => !part || part === \".\" || part === \"..\"))\n throw new Error(`invalid fixture path: ${relativePath}`);\n return parts;\n}\n\nfunction validFixturePath(path: string): boolean {\n return !!path && !isAbsolute(path) && !path.includes(\"\\\\\") && !/:/.test(path);\n}\n\n/** Refuse fixture or setup-created links before mounting trusted artifact bytes. */\nexport async function safePreparationTarget(\n workspace: string,\n relativePath: string,\n): Promise<string> {\n const parts = fixtureParts(relativePath);\n let parent = workspace;\n for (const part of parts.slice(0, -1)) {\n parent = join(parent, part);\n await preparationParent(parent);\n }\n const target = join(workspace, ...parts);\n await requireUnusedPreparationTarget(target);\n return target;\n}\n\nasync function preparationParent(path: string): Promise<void> {\n try {\n const entry = await lstat(path);\n if (!entry.isDirectory() || entry.isSymbolicLink())\n throw new Error(\"preparation artifact traverses a non-directory\");\n } catch (error) {\n if (!isMissingFile(error)) throw error;\n await mkdir(path, { mode: 0o700 });\n }\n}\nasync function requireUnusedPreparationTarget(path: string): Promise<void> {\n try {\n await lstat(path);\n throw new Error(\"preparation artifact path already exists\");\n } catch (error) {\n if (!isMissingFile(error)) throw error;\n }\n}\nfunction collidingPaths(left: string, right: string): boolean {\n return (\n left === right ||\n left.startsWith(`${right}/`) ||\n right.startsWith(`${left}/`)\n );\n}\n\nfunction validatePaths(paths: string[]): void {\n const normalized = paths.map((path) =>\n fixtureParts(path).join(\"/\").toLowerCase(),\n );\n for (const [index, path] of normalized.entries())\n for (const other of normalized.slice(0, index))\n if (collidingPaths(path, other)) throw new Error(\"fixture paths collide\");\n}\n\n/** Decode declared inline artifacts before creating a candidate workspace. */\nexport function prepareInlineArtifacts(\n declarations: PreparationArtifact[],\n fixturePaths: string[],\n): InlineArtifact[] {\n validatePaths([\n ...fixturePaths,\n ...declarations.map((item) => item.relativePath),\n ]);\n const state = { ids: new Set<string>(), bytes: 0 };\n return declarations.map((item) => inlineArtifact(item, state));\n}\n\nfunction requireArtifactFlags(item: PreparationArtifact): void {\n if (item.gitExclude !== undefined && typeof item.gitExclude !== \"boolean\")\n throw new Error(\"preparation artifact gitExclude must be boolean\");\n if (item.executable !== undefined && typeof item.executable !== \"boolean\")\n throw new Error(\"preparation artifact executable must be boolean\");\n}\nfunction requireInlineSource(\n item: PreparationArtifact,\n): asserts item is PreparationArtifact & { contentBase64: string } {\n if (item.gitExclude && containsControlCharacter(item.relativePath))\n throw new Error(\"Git-excluded artifact path contains control characters\");\n if (item.sourceRef !== undefined || typeof item.contentBase64 !== \"string\")\n throw new Error(\"preparation source references are not supported\");\n}\nfunction canonicalBase64Bytes(encoded: string): Buffer {\n if (\n !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(\n encoded,\n )\n )\n throw new Error(\"preparation artifact content is not canonical base64\");\n const bytes = Buffer.from(encoded, \"base64\");\n if (bytes.toString(\"base64\") !== encoded)\n throw new Error(\"preparation artifact content is not canonical base64\");\n return bytes;\n}\nfunction requireArtifactSize(bytes: number, total: number): void {\n if (bytes > MAX_ARTIFACT_BYTES || total > MAX_TOTAL_BYTES)\n throw new Error(\"preparation artifacts exceed the size limit\");\n}\nfunction artifactFlags(item: PreparationArtifact) {\n return {\n ...(item.gitExclude === undefined ? {} : { gitExclude: item.gitExclude }),\n ...(item.executable === undefined ? {} : { executable: item.executable }),\n };\n}\nfunction inlineArtifact(\n item: PreparationArtifact,\n state: { ids: Set<string>; bytes: number },\n): InlineArtifact {\n if (!item.id || state.ids.has(item.id))\n throw new Error(\"preparation artifact IDs must be unique and nonempty\");\n state.ids.add(item.id);\n requireArtifactFlags(item);\n requireInlineSource(item);\n const bytes = canonicalBase64Bytes(item.contentBase64);\n state.bytes += bytes.byteLength;\n requireArtifactSize(bytes.byteLength, state.bytes);\n if (sha256(bytes) !== item.sha256)\n throw new Error(\"preparation artifact digest does not match content\");\n return {\n id: item.id,\n relativePath: item.relativePath,\n sha256: item.sha256,\n bytes,\n ...artifactFlags(item),\n };\n}\n\n/** Resolve opaque source IDs only through an explicitly declared case source map. */\nexport async function prepareArtifacts(\n declarations: PreparationArtifact[],\n fixturePaths: string[],\n sources?: PreparationSources,\n): Promise<InlineArtifact[]> {\n if (!declarations.some((item) => item.sourceRef !== undefined))\n return prepareInlineArtifacts(declarations, fixturePaths);\n const context = await preparationSourceContext(sources);\n const resolved: PreparationArtifact[] = [];\n const state = { bytes: 0 };\n for (const item of declarations)\n resolved.push(await resolveSourceArtifact(item, context, state));\n return prepareInlineArtifacts(resolved, fixturePaths);\n}\n\nasync function preparationSourceContext(\n sources: PreparationSources | undefined,\n) {\n if (!sources || !isAbsolute(sources.root))\n throw new Error(\"preparation source root is required and must be absolute\");\n const root = await realpath(sources.root).catch((cause: unknown) => {\n throw new Error(\"preparation source root is unreadable\", { cause });\n });\n return { root, refs: sources.refs };\n}\nfunction sourceArtifactUrl(\n item: PreparationArtifact & { sourceRef: string },\n refs: Record<string, string>,\n): string {\n if (item.contentBase64 !== undefined)\n throw new Error(\"preparation artifact has two content sources\");\n if (!Object.hasOwn(refs, item.sourceRef))\n throw new Error(\"preparation source reference is not declared\");\n const url = refs[item.sourceRef];\n if (typeof url !== \"string\" || !url.startsWith(\"file:///\"))\n throw new Error(\"preparation source reference must be a file URL\");\n return url;\n}\nasync function preparationSourcePath(url: string): Promise<string> {\n try {\n return await realpath(fileURLToPath(url));\n } catch (cause) {\n throw new Error(\"preparation source is unreadable\", { cause });\n }\n}\nfunction requireContainedSource(root: string, path: string): void {\n if (root === path || !insideRoot(root, path))\n throw new Error(\"preparation source escapes its declared root\");\n}\nasync function preparationSourceSize(path: string): Promise<number> {\n try {\n return (await stat(path)).size;\n } catch (cause) {\n throw new Error(\"preparation source is unreadable\", { cause });\n }\n}\nasync function preparationSourceBytes(path: string): Promise<Buffer> {\n try {\n return await readFile(path);\n } catch (cause) {\n throw new Error(\"preparation source is unreadable\", { cause });\n }\n}\nasync function resolveSourceArtifact(\n item: PreparationArtifact,\n context: Awaited<ReturnType<typeof preparationSourceContext>>,\n state: { bytes: number },\n): Promise<PreparationArtifact> {\n if (item.sourceRef === undefined) return item;\n const url = sourceArtifactUrl(\n { ...item, sourceRef: item.sourceRef },\n context.refs,\n );\n const path = await preparationSourcePath(url);\n requireContainedSource(context.root, path);\n const size = await preparationSourceSize(path);\n requireArtifactSize(size, state.bytes + size);\n const bytes = await preparationSourceBytes(path);\n state.bytes += bytes.byteLength;\n requireArtifactSize(bytes.byteLength, state.bytes);\n return {\n id: item.id,\n relativePath: item.relativePath,\n sha256: item.sha256,\n contentBase64: bytes.toString(\"base64\"),\n ...artifactFlags(item),\n };\n}\n"]}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { isRecord, isStringArray } from "../value-guards.js";
|
|
2
|
+
import { isMissingFile } from "./tools.js";
|
|
3
|
+
import { lstat, mkdir, realpath, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { isAbsolute, join } from "node:path";
|
|
5
|
+
import { insideRoot } from "../paths.js";
|
|
6
|
+
import { runGit } from "../git.js";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { canonicalJson } from "../identity.js";
|
|
9
|
+
import { prepareFixtureBin } from "./bin.js";
|
|
10
|
+
import { prepareGitHooks } from "./git-hooks.js";
|
|
11
|
+
import { fixtureParts } from "./preparation.js";
|
|
12
|
+
const MAX_GIT_OUTPUT_BYTES = 64 * 1024;
|
|
13
|
+
const MAX_OVERLAY_FILES = 1024;
|
|
14
|
+
const MAX_OVERLAY_BYTES = 32 * 1024 * 1024;
|
|
15
|
+
function repositoryRecord(value) {
|
|
16
|
+
if (!isRecord(value))
|
|
17
|
+
throw new Error("invalid repository fixture");
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
function requireRepositoryIdentity(fixture) {
|
|
21
|
+
const allowed = [
|
|
22
|
+
"kind",
|
|
23
|
+
"sourceRef",
|
|
24
|
+
"files",
|
|
25
|
+
"staged",
|
|
26
|
+
"commitFiles",
|
|
27
|
+
"hooks",
|
|
28
|
+
"bin",
|
|
29
|
+
];
|
|
30
|
+
if (fixture.kind !== "repository" ||
|
|
31
|
+
Object.keys(fixture).some((key) => !allowed.includes(key)) ||
|
|
32
|
+
typeof fixture.sourceRef !== "string" ||
|
|
33
|
+
!fixture.sourceRef)
|
|
34
|
+
throw new Error("invalid repository fixture");
|
|
35
|
+
}
|
|
36
|
+
function optionalRepositoryFiles(value) {
|
|
37
|
+
return (value === undefined ||
|
|
38
|
+
(isRecord(value) &&
|
|
39
|
+
Object.values(value).every((item) => typeof item === "string")));
|
|
40
|
+
}
|
|
41
|
+
function optionalRepositoryStaging(value) {
|
|
42
|
+
return value === undefined || isStringArray(value);
|
|
43
|
+
}
|
|
44
|
+
function optionalRepositoryCommit(value) {
|
|
45
|
+
return value === undefined || typeof value === "boolean";
|
|
46
|
+
}
|
|
47
|
+
function requireRepositoryOptions(fixture) {
|
|
48
|
+
if (!optionalRepositoryFiles(fixture.files) ||
|
|
49
|
+
!optionalRepositoryStaging(fixture.staged) ||
|
|
50
|
+
!optionalRepositoryCommit(fixture.commitFiles))
|
|
51
|
+
throw new Error("invalid repository fixture");
|
|
52
|
+
}
|
|
53
|
+
function requireRepositoryTools(fixture) {
|
|
54
|
+
prepareGitHooks(fixture.hooks);
|
|
55
|
+
prepareFixtureBin(fixture.bin);
|
|
56
|
+
}
|
|
57
|
+
function repositoryFixtureData(value) {
|
|
58
|
+
const fixture = repositoryRecord(value);
|
|
59
|
+
requireRepositoryIdentity(fixture);
|
|
60
|
+
requireRepositoryOptions(fixture);
|
|
61
|
+
requireRepositoryTools(fixture);
|
|
62
|
+
return fixture;
|
|
63
|
+
}
|
|
64
|
+
function requireOverlayPath(path) {
|
|
65
|
+
if (fixtureParts(path).some((part) => part.toLowerCase() === ".git"))
|
|
66
|
+
throw new Error("repository fixture cannot write repository metadata");
|
|
67
|
+
}
|
|
68
|
+
function overlayPathsCollide(left, right) {
|
|
69
|
+
return (left === right ||
|
|
70
|
+
left.startsWith(`${right}/`) ||
|
|
71
|
+
right.startsWith(`${left}/`));
|
|
72
|
+
}
|
|
73
|
+
function requireDistinctOverlayPaths(paths) {
|
|
74
|
+
const normalized = paths.map((path) => path.toLowerCase());
|
|
75
|
+
for (const [index, path] of normalized.entries())
|
|
76
|
+
for (const other of normalized.slice(0, index))
|
|
77
|
+
if (overlayPathsCollide(path, other))
|
|
78
|
+
throw new Error("repository fixture overlay paths collide");
|
|
79
|
+
}
|
|
80
|
+
function requireBoundedOverlay(files, staged) {
|
|
81
|
+
const paths = Object.keys(files);
|
|
82
|
+
if (paths.length > MAX_OVERLAY_FILES)
|
|
83
|
+
throw new Error("repository fixture exceeds the file limit");
|
|
84
|
+
let bytes = 0;
|
|
85
|
+
for (const [path, content] of Object.entries(files)) {
|
|
86
|
+
requireOverlayPath(path);
|
|
87
|
+
bytes +=
|
|
88
|
+
Buffer.byteLength(path, "utf8") + Buffer.byteLength(content, "utf8");
|
|
89
|
+
}
|
|
90
|
+
bytes += staged.reduce((size, path) => size + Buffer.byteLength(path, "utf8"), 0);
|
|
91
|
+
if (bytes > MAX_OVERLAY_BYTES)
|
|
92
|
+
throw new Error("repository fixture exceeds the size limit");
|
|
93
|
+
requireDistinctOverlayPaths(paths);
|
|
94
|
+
}
|
|
95
|
+
function requireRepositoryStaging(fixture, files, staged) {
|
|
96
|
+
if (new Set(staged.map((path) => path.toLowerCase())).size !== staged.length ||
|
|
97
|
+
staged.some((path) => !Object.hasOwn(files, path)) ||
|
|
98
|
+
(fixture.commitFiles === true && Object.keys(files).length === 0))
|
|
99
|
+
throw new Error("invalid repository fixture staging");
|
|
100
|
+
}
|
|
101
|
+
/** Validate repository source identity and bounded working-tree changes. */
|
|
102
|
+
export function prepareRepositoryFixture(value) {
|
|
103
|
+
const fixture = repositoryFixtureData(value);
|
|
104
|
+
const files = fixture.files ?? {}, staged = fixture.staged ?? [];
|
|
105
|
+
requireBoundedOverlay(files, staged);
|
|
106
|
+
requireRepositoryStaging(fixture, files, staged);
|
|
107
|
+
const clone = JSON.parse(canonicalJson(fixture));
|
|
108
|
+
return repositoryFixtureData(clone);
|
|
109
|
+
}
|
|
110
|
+
async function git(args, cwd) {
|
|
111
|
+
const stdout = await runGit({
|
|
112
|
+
cwd,
|
|
113
|
+
args,
|
|
114
|
+
identity: "fixture",
|
|
115
|
+
label: "repository fixture",
|
|
116
|
+
maxOutputBytes: MAX_GIT_OUTPUT_BYTES,
|
|
117
|
+
validateUtf8: true,
|
|
118
|
+
});
|
|
119
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(stdout).trim();
|
|
120
|
+
}
|
|
121
|
+
/** Resolve a declared local repository without accepting a dirty source tree. */
|
|
122
|
+
export async function resolveRepositorySource(sourceRef, sources) {
|
|
123
|
+
const selected = repositorySourceSelection(sourceRef, sources);
|
|
124
|
+
const { root, path } = await canonicalRepositorySource(selected);
|
|
125
|
+
requireContainedRepository(root, path);
|
|
126
|
+
return { path, revision: await repositoryCommit(path) };
|
|
127
|
+
}
|
|
128
|
+
function requireRepositorySources(sources) {
|
|
129
|
+
if (!sources || !isAbsolute(sources.root))
|
|
130
|
+
throw new Error("repository source root is required and must be absolute");
|
|
131
|
+
}
|
|
132
|
+
function repositorySourceSelection(sourceRef, sources) {
|
|
133
|
+
requireRepositorySources(sources);
|
|
134
|
+
if (!Object.hasOwn(sources.refs, sourceRef))
|
|
135
|
+
throw new Error("repository source reference is not declared");
|
|
136
|
+
const url = sources.refs[sourceRef];
|
|
137
|
+
if (typeof url !== "string" || !url.startsWith("file:///"))
|
|
138
|
+
throw new Error("repository source reference must be a file URL");
|
|
139
|
+
return { root: sources.root, url };
|
|
140
|
+
}
|
|
141
|
+
async function canonicalRepositorySource(selected) {
|
|
142
|
+
try {
|
|
143
|
+
const [root, path] = await Promise.all([
|
|
144
|
+
realpath(selected.root),
|
|
145
|
+
realpath(fileURLToPath(selected.url)),
|
|
146
|
+
]);
|
|
147
|
+
if (!(await stat(path)).isDirectory())
|
|
148
|
+
throw new Error();
|
|
149
|
+
return { root, path };
|
|
150
|
+
}
|
|
151
|
+
catch (cause) {
|
|
152
|
+
throw new Error("repository source is unreadable", { cause });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function requireContainedRepository(root, path) {
|
|
156
|
+
if (!insideRoot(root, path))
|
|
157
|
+
throw new Error("repository source escapes its declared root");
|
|
158
|
+
}
|
|
159
|
+
async function repositoryCommit(path) {
|
|
160
|
+
const top = await git(["rev-parse", "--show-toplevel"], path);
|
|
161
|
+
if ((await realpath(top)) !== path)
|
|
162
|
+
throw new Error("repository source must name the repository root");
|
|
163
|
+
const revision = await git(["rev-parse", "--verify", "HEAD^{commit}"], path);
|
|
164
|
+
if (!/^[a-f0-9]{40,64}$/.test(revision))
|
|
165
|
+
throw new Error("repository source has no valid commit");
|
|
166
|
+
if (await git([
|
|
167
|
+
"status",
|
|
168
|
+
"--porcelain=v1",
|
|
169
|
+
"--untracked-files=all",
|
|
170
|
+
"--ignore-submodules=none",
|
|
171
|
+
], path))
|
|
172
|
+
throw new Error("repository source has uncommitted changes");
|
|
173
|
+
return revision;
|
|
174
|
+
}
|
|
175
|
+
function requireRepositorySnapshot(current, expected, message) {
|
|
176
|
+
if (current.path !== expected.path || current.revision !== expected.revision)
|
|
177
|
+
throw new Error(message);
|
|
178
|
+
}
|
|
179
|
+
/** Clone a fixed commit without hardlinks or a remote back to the source. */
|
|
180
|
+
export async function cloneRepositorySource(sourceRef, sources, expected, workspace) {
|
|
181
|
+
const current = await resolveRepositorySource(sourceRef, sources);
|
|
182
|
+
requireRepositorySnapshot(current, expected, "repository source changed during run");
|
|
183
|
+
await git([
|
|
184
|
+
"clone",
|
|
185
|
+
"--local",
|
|
186
|
+
"--no-hardlinks",
|
|
187
|
+
"--no-checkout",
|
|
188
|
+
"--no-recurse-submodules",
|
|
189
|
+
"--quiet",
|
|
190
|
+
current.path,
|
|
191
|
+
workspace,
|
|
192
|
+
], workspace);
|
|
193
|
+
await git(["checkout", "--detach", "--quiet", expected.revision], workspace);
|
|
194
|
+
if ((await git(["rev-parse", "HEAD"], workspace)) !== expected.revision)
|
|
195
|
+
throw new Error("repository fixture revision changed during clone");
|
|
196
|
+
for (const remote of (await git(["remote"], workspace))
|
|
197
|
+
.split("\n")
|
|
198
|
+
.filter(Boolean))
|
|
199
|
+
await git(["remote", "remove", remote], workspace);
|
|
200
|
+
const after = await resolveRepositorySource(sourceRef, sources);
|
|
201
|
+
requireRepositorySnapshot(after, expected, "repository source changed during clone");
|
|
202
|
+
}
|
|
203
|
+
async function safeOverlayTarget(workspace, path) {
|
|
204
|
+
const parts = fixtureParts(path);
|
|
205
|
+
let parent = workspace;
|
|
206
|
+
for (const part of parts.slice(0, -1)) {
|
|
207
|
+
parent = join(parent, part);
|
|
208
|
+
await overlayParent(parent);
|
|
209
|
+
}
|
|
210
|
+
const target = join(workspace, ...parts);
|
|
211
|
+
await requireOverlayTarget(target);
|
|
212
|
+
return target;
|
|
213
|
+
}
|
|
214
|
+
async function overlayParent(path) {
|
|
215
|
+
try {
|
|
216
|
+
const entry = await lstat(path);
|
|
217
|
+
if (!entry.isDirectory() || entry.isSymbolicLink())
|
|
218
|
+
throw new Error("repository fixture overlay traverses a non-directory");
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
if (!isMissingFile(error))
|
|
222
|
+
throw error;
|
|
223
|
+
await mkdir(path, { mode: 0o700 });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async function requireOverlayTarget(path) {
|
|
227
|
+
try {
|
|
228
|
+
const entry = await lstat(path);
|
|
229
|
+
if (!entry.isFile() || entry.isSymbolicLink())
|
|
230
|
+
throw new Error("repository fixture overlay targets a non-file");
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
if (!isMissingFile(error))
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/** Apply declared files after cloning, optionally committing or staging them. */
|
|
238
|
+
export async function applyRepositoryOverlay(fixture, workspace) {
|
|
239
|
+
const files = fixture.files ?? {};
|
|
240
|
+
for (const [path, content] of Object.entries(files))
|
|
241
|
+
await writeFile(await safeOverlayTarget(workspace, path), content, {
|
|
242
|
+
flag: "w",
|
|
243
|
+
mode: 0o600,
|
|
244
|
+
});
|
|
245
|
+
await stageRepositoryOverlay(fixture, workspace, Object.keys(files));
|
|
246
|
+
}
|
|
247
|
+
async function stageRepositoryOverlay(fixture, workspace, paths) {
|
|
248
|
+
if (fixture.commitFiles) {
|
|
249
|
+
await git(["add", "--", ...paths], workspace);
|
|
250
|
+
await git(["commit", "--quiet", "-m", "Add evaluation scaffolding"], workspace);
|
|
251
|
+
}
|
|
252
|
+
if (fixture.staged?.length)
|
|
253
|
+
await git(["add", "--", ...fixture.staged], workspace);
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=repository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repository.js","sourceRoot":"","sources":["repository.ts"],"names":[],"mappings":"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","sourcesContent":["import { isRecord, isStringArray } from \"../value-guards\";\nimport { isMissingFile } from \"./tools\";\nimport { lstat, mkdir, realpath, stat, writeFile } from \"node:fs/promises\";\nimport { isAbsolute, join } from \"node:path\";\nimport { insideRoot } from \"../paths\";\nimport { runGit } from \"../git\";\nimport { fileURLToPath } from \"node:url\";\nimport { canonicalJson } from \"../identity\";\nimport { prepareFixtureBin } from \"./bin\";\nimport { prepareGitHooks } from \"./git-hooks\";\nimport type { PreparationSources } from \"./preparation\";\nimport { fixtureParts } from \"./preparation\";\n\nconst MAX_GIT_OUTPUT_BYTES = 64 * 1024;\nconst MAX_OVERLAY_FILES = 1024;\nconst MAX_OVERLAY_BYTES = 32 * 1024 * 1024;\n\nexport interface RepositoryFixture {\n kind: \"repository\";\n sourceRef: string;\n files?: Record<string, string>;\n staged?: string[];\n commitFiles?: boolean;\n hooks?: Record<string, string>;\n bin?: Record<string, string>;\n}\n\nfunction repositoryRecord(value: unknown): Record<string, unknown> {\n if (!isRecord(value)) throw new Error(\"invalid repository fixture\");\n return value;\n}\nfunction requireRepositoryIdentity(\n fixture: Record<string, unknown>,\n): asserts fixture is Record<string, unknown> & {\n kind: \"repository\";\n sourceRef: string;\n} {\n const allowed = [\n \"kind\",\n \"sourceRef\",\n \"files\",\n \"staged\",\n \"commitFiles\",\n \"hooks\",\n \"bin\",\n ];\n if (\n fixture.kind !== \"repository\" ||\n Object.keys(fixture).some((key) => !allowed.includes(key)) ||\n typeof fixture.sourceRef !== \"string\" ||\n !fixture.sourceRef\n )\n throw new Error(\"invalid repository fixture\");\n}\nfunction optionalRepositoryFiles(\n value: unknown,\n): value is Record<string, string> | undefined {\n return (\n value === undefined ||\n (isRecord(value) &&\n Object.values(value).every((item) => typeof item === \"string\"))\n );\n}\nfunction optionalRepositoryStaging(\n value: unknown,\n): value is string[] | undefined {\n return value === undefined || isStringArray(value);\n}\nfunction optionalRepositoryCommit(\n value: unknown,\n): value is boolean | undefined {\n return value === undefined || typeof value === \"boolean\";\n}\nfunction requireRepositoryOptions(\n fixture: Record<string, unknown>,\n): asserts fixture is Record<string, unknown> & {\n files?: Record<string, string>;\n staged?: string[];\n commitFiles?: boolean;\n} {\n if (\n !optionalRepositoryFiles(fixture.files) ||\n !optionalRepositoryStaging(fixture.staged) ||\n !optionalRepositoryCommit(fixture.commitFiles)\n )\n throw new Error(\"invalid repository fixture\");\n}\nfunction requireRepositoryTools(\n fixture: Record<string, unknown>,\n): asserts fixture is Record<string, unknown> & {\n hooks?: Record<string, string>;\n bin?: Record<string, string>;\n} {\n prepareGitHooks(fixture.hooks);\n prepareFixtureBin(fixture.bin);\n}\nfunction repositoryFixtureData(value: unknown): RepositoryFixture {\n const fixture = repositoryRecord(value);\n requireRepositoryIdentity(fixture);\n requireRepositoryOptions(fixture);\n requireRepositoryTools(fixture);\n return fixture;\n}\nfunction requireOverlayPath(path: string): void {\n if (fixtureParts(path).some((part) => part.toLowerCase() === \".git\"))\n throw new Error(\"repository fixture cannot write repository metadata\");\n}\nfunction overlayPathsCollide(left: string, right: string): boolean {\n return (\n left === right ||\n left.startsWith(`${right}/`) ||\n right.startsWith(`${left}/`)\n );\n}\nfunction requireDistinctOverlayPaths(paths: string[]): void {\n const normalized = paths.map((path) => path.toLowerCase());\n for (const [index, path] of normalized.entries())\n for (const other of normalized.slice(0, index))\n if (overlayPathsCollide(path, other))\n throw new Error(\"repository fixture overlay paths collide\");\n}\nfunction requireBoundedOverlay(\n files: Record<string, string>,\n staged: string[],\n): void {\n const paths = Object.keys(files);\n if (paths.length > MAX_OVERLAY_FILES)\n throw new Error(\"repository fixture exceeds the file limit\");\n let bytes = 0;\n for (const [path, content] of Object.entries(files)) {\n requireOverlayPath(path);\n bytes +=\n Buffer.byteLength(path, \"utf8\") + Buffer.byteLength(content, \"utf8\");\n }\n bytes += staged.reduce(\n (size, path) => size + Buffer.byteLength(path, \"utf8\"),\n 0,\n );\n if (bytes > MAX_OVERLAY_BYTES)\n throw new Error(\"repository fixture exceeds the size limit\");\n requireDistinctOverlayPaths(paths);\n}\nfunction requireRepositoryStaging(\n fixture: RepositoryFixture,\n files: Record<string, string>,\n staged: string[],\n): void {\n if (\n new Set(staged.map((path) => path.toLowerCase())).size !== staged.length ||\n staged.some((path) => !Object.hasOwn(files, path)) ||\n (fixture.commitFiles === true && Object.keys(files).length === 0)\n )\n throw new Error(\"invalid repository fixture staging\");\n}\n\n/** Validate repository source identity and bounded working-tree changes. */\nexport function prepareRepositoryFixture(value: unknown): RepositoryFixture {\n const fixture = repositoryFixtureData(value);\n const files = fixture.files ?? {},\n staged = fixture.staged ?? [];\n requireBoundedOverlay(files, staged);\n requireRepositoryStaging(fixture, files, staged);\n const clone: unknown = JSON.parse(canonicalJson(fixture));\n return repositoryFixtureData(clone);\n}\n\nexport interface RepositorySource {\n path: string;\n revision: string;\n}\n\nasync function git(args: string[], cwd: string): Promise<string> {\n const stdout = await runGit({\n cwd,\n args,\n identity: \"fixture\",\n label: \"repository fixture\",\n maxOutputBytes: MAX_GIT_OUTPUT_BYTES,\n validateUtf8: true,\n });\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(stdout).trim();\n}\n\n/** Resolve a declared local repository without accepting a dirty source tree. */\nexport async function resolveRepositorySource(\n sourceRef: string,\n sources?: PreparationSources,\n): Promise<RepositorySource> {\n const selected = repositorySourceSelection(sourceRef, sources);\n const { root, path } = await canonicalRepositorySource(selected);\n requireContainedRepository(root, path);\n return { path, revision: await repositoryCommit(path) };\n}\n\nfunction requireRepositorySources(\n sources: PreparationSources | undefined,\n): asserts sources is PreparationSources {\n if (!sources || !isAbsolute(sources.root))\n throw new Error(\"repository source root is required and must be absolute\");\n}\nfunction repositorySourceSelection(\n sourceRef: string,\n sources: PreparationSources | undefined,\n) {\n requireRepositorySources(sources);\n if (!Object.hasOwn(sources.refs, sourceRef))\n throw new Error(\"repository source reference is not declared\");\n const url = sources.refs[sourceRef];\n if (typeof url !== \"string\" || !url.startsWith(\"file:///\"))\n throw new Error(\"repository source reference must be a file URL\");\n return { root: sources.root, url };\n}\nasync function canonicalRepositorySource(selected: {\n root: string;\n url: string;\n}) {\n try {\n const [root, path] = await Promise.all([\n realpath(selected.root),\n realpath(fileURLToPath(selected.url)),\n ]);\n if (!(await stat(path)).isDirectory()) throw new Error();\n return { root, path };\n } catch (cause) {\n throw new Error(\"repository source is unreadable\", { cause });\n }\n}\nfunction requireContainedRepository(root: string, path: string): void {\n if (!insideRoot(root, path))\n throw new Error(\"repository source escapes its declared root\");\n}\nasync function repositoryCommit(path: string): Promise<string> {\n const top = await git([\"rev-parse\", \"--show-toplevel\"], path);\n if ((await realpath(top)) !== path)\n throw new Error(\"repository source must name the repository root\");\n const revision = await git([\"rev-parse\", \"--verify\", \"HEAD^{commit}\"], path);\n if (!/^[a-f0-9]{40,64}$/.test(revision))\n throw new Error(\"repository source has no valid commit\");\n if (\n await git(\n [\n \"status\",\n \"--porcelain=v1\",\n \"--untracked-files=all\",\n \"--ignore-submodules=none\",\n ],\n path,\n )\n )\n throw new Error(\"repository source has uncommitted changes\");\n return revision;\n}\nfunction requireRepositorySnapshot(\n current: RepositorySource,\n expected: RepositorySource,\n message: string,\n): void {\n if (current.path !== expected.path || current.revision !== expected.revision)\n throw new Error(message);\n}\n\n/** Clone a fixed commit without hardlinks or a remote back to the source. */\nexport async function cloneRepositorySource(\n sourceRef: string,\n sources: PreparationSources,\n expected: RepositorySource,\n workspace: string,\n): Promise<void> {\n const current = await resolveRepositorySource(sourceRef, sources);\n requireRepositorySnapshot(\n current,\n expected,\n \"repository source changed during run\",\n );\n await git(\n [\n \"clone\",\n \"--local\",\n \"--no-hardlinks\",\n \"--no-checkout\",\n \"--no-recurse-submodules\",\n \"--quiet\",\n current.path,\n workspace,\n ],\n workspace,\n );\n await git([\"checkout\", \"--detach\", \"--quiet\", expected.revision], workspace);\n if ((await git([\"rev-parse\", \"HEAD\"], workspace)) !== expected.revision)\n throw new Error(\"repository fixture revision changed during clone\");\n for (const remote of (await git([\"remote\"], workspace))\n .split(\"\\n\")\n .filter(Boolean))\n await git([\"remote\", \"remove\", remote], workspace);\n const after = await resolveRepositorySource(sourceRef, sources);\n requireRepositorySnapshot(\n after,\n expected,\n \"repository source changed during clone\",\n );\n}\n\nasync function safeOverlayTarget(\n workspace: string,\n path: string,\n): Promise<string> {\n const parts = fixtureParts(path);\n let parent = workspace;\n for (const part of parts.slice(0, -1)) {\n parent = join(parent, part);\n await overlayParent(parent);\n }\n const target = join(workspace, ...parts);\n await requireOverlayTarget(target);\n return target;\n}\n\nasync function overlayParent(path: string): Promise<void> {\n try {\n const entry = await lstat(path);\n if (!entry.isDirectory() || entry.isSymbolicLink())\n throw new Error(\"repository fixture overlay traverses a non-directory\");\n } catch (error) {\n if (!isMissingFile(error)) throw error;\n await mkdir(path, { mode: 0o700 });\n }\n}\nasync function requireOverlayTarget(path: string): Promise<void> {\n try {\n const entry = await lstat(path);\n if (!entry.isFile() || entry.isSymbolicLink())\n throw new Error(\"repository fixture overlay targets a non-file\");\n } catch (error) {\n if (!isMissingFile(error)) throw error;\n }\n}\n\n/** Apply declared files after cloning, optionally committing or staging them. */\nexport async function applyRepositoryOverlay(\n fixture: RepositoryFixture,\n workspace: string,\n): Promise<void> {\n const files = fixture.files ?? {};\n for (const [path, content] of Object.entries(files))\n await writeFile(await safeOverlayTarget(workspace, path), content, {\n flag: \"w\",\n mode: 0o600,\n });\n await stageRepositoryOverlay(fixture, workspace, Object.keys(files));\n}\nasync function stageRepositoryOverlay(\n fixture: RepositoryFixture,\n workspace: string,\n paths: string[],\n): Promise<void> {\n if (fixture.commitFiles) {\n await git([\"add\", \"--\", ...paths], workspace);\n await git(\n [\"commit\", \"--quiet\", \"-m\", \"Add evaluation scaffolding\"],\n workspace,\n );\n }\n if (fixture.staged?.length)\n await git([\"add\", \"--\", ...fixture.staged], workspace);\n}\n"]}
|