Introduce version 4 PromptKit configuration
This commit is contained in:
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
In progress. The dependency and framework adapter replacement is implemented;
|
In progress. The dependency, framework adapter, and version 4 PromptKit
|
||||||
the configuration, module asset terminology, and remaining provenance alignment
|
configuration migration are implemented; module asset terminology and remaining
|
||||||
are still planned.
|
provenance alignment are still planned.
|
||||||
|
|
||||||
## Objective
|
## Objective
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
version: 3
|
version: 4
|
||||||
concurrency:
|
concurrency:
|
||||||
total_llm: 2
|
total_llm: 2
|
||||||
stage_workers:
|
stage_workers:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
version: 3
|
version: 4
|
||||||
pipelines:
|
pipelines:
|
||||||
dnd-session:
|
dnd-session:
|
||||||
input: seriatim
|
input: seriatim
|
||||||
|
|||||||
@@ -169,8 +169,8 @@ func buildProductionLLMClient(ctx context.Context, cfg config.Config, profileID
|
|||||||
}
|
}
|
||||||
recorder := llm.NewLLMProfileRecorder()
|
recorder := llm.NewLLMProfileRecorder()
|
||||||
client, err := llm.NewPromptKitClient(llm.PromptKitClientConfig{
|
client, err := llm.NewPromptKitClient(llm.PromptKitClientConfig{
|
||||||
ProfileDir: cfg.Scriptorium.ProfileDir,
|
ProfileDir: cfg.PromptKit.ProfileDir,
|
||||||
ProfileFile: cfg.Scriptorium.ProfileFile,
|
ProfileFile: cfg.PromptKit.ProfileFile,
|
||||||
Assets: assets,
|
Assets: assets,
|
||||||
Recorder: recorder,
|
Recorder: recorder,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -214,13 +214,13 @@ func commandContractOptionsWithLookup(t *testing.T, lookup func(string) (string,
|
|||||||
|
|
||||||
func writeCommandConfig(t *testing.T, firstID, secondID string) string {
|
func writeCommandConfig(t *testing.T, firstID, secondID string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
content := fmt.Sprintf("version: 3\npipelines:\n %q:\n input: seriatim\n %q:\n input: seriatim\n", firstID, secondID)
|
content := fmt.Sprintf("version: 4\npipelines:\n %q:\n input: seriatim\n %q:\n input: seriatim\n", firstID, secondID)
|
||||||
return writeCommandConfigContent(t, content)
|
return writeCommandConfigContent(t, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeResolvableCommandConfig(t *testing.T) string {
|
func writeResolvableCommandConfig(t *testing.T) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
return writeCommandConfigContent(t, `version: 3
|
return writeCommandConfigContent(t, `version: 4
|
||||||
pipelines:
|
pipelines:
|
||||||
demo:
|
demo:
|
||||||
input: seriatim
|
input: seriatim
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func TestOversizedNPCRegistryFailsBeforeRuntimeAndCheckpointConstruction(t *test
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
checkpointRoot := filepath.Join(t.TempDir(), "checkpoints")
|
checkpointRoot := filepath.Join(t.TempDir(), "checkpoints")
|
||||||
content := fmt.Sprintf(`version: 3
|
content := fmt.Sprintf(`version: 4
|
||||||
cache:
|
cache:
|
||||||
chunk_plans:
|
chunk_plans:
|
||||||
mode: bypass
|
mode: bypass
|
||||||
|
|||||||
@@ -583,7 +583,7 @@ func maintainedExampleFiles(t *testing.T) []maintainedExample {
|
|||||||
|
|
||||||
func productionSpellCatalogContractConfig(t *testing.T) string {
|
func productionSpellCatalogContractConfig(t *testing.T) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
return fmt.Sprintf(`version: 3
|
return fmt.Sprintf(`version: 4
|
||||||
cache:
|
cache:
|
||||||
chunk_plans:
|
chunk_plans:
|
||||||
mode: bypass
|
mode: bypass
|
||||||
@@ -680,7 +680,7 @@ func productionRunOptions(t *testing.T, fake *productionFakeLLMClient) Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func productionRunConfig(outputRoot, chunkModule string) string {
|
func productionRunConfig(outputRoot, chunkModule string) string {
|
||||||
return fmt.Sprintf(`version: 3
|
return fmt.Sprintf(`version: 4
|
||||||
output:
|
output:
|
||||||
directory: %q
|
directory: %q
|
||||||
cache:
|
cache:
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ output:
|
|||||||
`)},
|
`)},
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateExplicitScriptoriumProfiles(ctx context.Context, cfg config.Config, profileIDs []string) error {
|
func validateExplicitPromptKitProfiles(ctx context.Context, cfg config.Config, profileIDs []string) error {
|
||||||
if len(profileIDs) == 0 {
|
if len(profileIDs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
engine, err := newProfileValidationEngine(cfg)
|
engine, err := newProfileValidationEngine(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("load Scriptorium profiles: %w", err)
|
return fmt.Errorf("load PromptKit profiles: %w", err)
|
||||||
}
|
}
|
||||||
for _, profileID := range profileIDs {
|
for _, profileID := range profileIDs {
|
||||||
if _, err := engine.Prepare(ctx, promptkit.RunRequest{
|
if _, err := engine.Prepare(ctx, promptkit.RunRequest{
|
||||||
@@ -46,9 +46,9 @@ func validateExplicitScriptoriumProfiles(ctx context.Context, cfg config.Config,
|
|||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
if errors.Is(err, promptkit.ErrProfileNotFound) {
|
if errors.Is(err, promptkit.ErrProfileNotFound) {
|
||||||
return fmt.Errorf("Scriptorium profile %q is not configured", profileID)
|
return fmt.Errorf("PromptKit profile %q is not configured", profileID)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("validate Scriptorium profile %q: %w", profileID, err)
|
return fmt.Errorf("validate PromptKit profile %q: %w", profileID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -58,11 +58,11 @@ func newProfileValidationEngine(cfg config.Config) (*promptkit.Engine, error) {
|
|||||||
opts := []promptkit.Option{
|
opts := []promptkit.Option{
|
||||||
promptkit.WithPromptFS(profileCheckPromptFS, "prompts"),
|
promptkit.WithPromptFS(profileCheckPromptFS, "prompts"),
|
||||||
}
|
}
|
||||||
if cfg.Scriptorium.ProfileFile != "" {
|
if cfg.PromptKit.ProfileFile != "" {
|
||||||
opts = append(opts, promptkit.WithProfileFile(cfg.Scriptorium.ProfileFile))
|
opts = append(opts, promptkit.WithProfileFile(cfg.PromptKit.ProfileFile))
|
||||||
}
|
}
|
||||||
return promptkit.NewEngine(promptkit.Config{
|
return promptkit.NewEngine(promptkit.Config{
|
||||||
PromptDir: "unused",
|
PromptDir: "unused",
|
||||||
ProfileDir: cfg.Scriptorium.ProfileDir,
|
ProfileDir: cfg.PromptKit.ProfileDir,
|
||||||
}, opts...)
|
}, opts...)
|
||||||
}
|
}
|
||||||
@@ -194,7 +194,7 @@ func (recomputeTestOutput) Encode(_ context.Context, req contracts.OutputRequest
|
|||||||
func newRecomputeTestRoots(t *testing.T) stateTestRoots {
|
func newRecomputeTestRoots(t *testing.T) stateTestRoots {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
roots := newStateTestRoots(t)
|
roots := newStateTestRoots(t)
|
||||||
config := fmt.Sprintf(`version: 3
|
config := fmt.Sprintf(`version: 4
|
||||||
output:
|
output:
|
||||||
directory: %q
|
directory: %q
|
||||||
cache:
|
cache:
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ func TestReferenceMaterializationSeparatesCLIAndConfigPathOrigins(t *testing.T)
|
|||||||
workingDir := t.TempDir()
|
workingDir := t.TempDir()
|
||||||
cfg := referenceContractConfig()
|
cfg := referenceContractConfig()
|
||||||
configPath := filepath.Join(configDir, "config.yml")
|
configPath := filepath.Join(configDir, "config.yml")
|
||||||
if err := os.WriteFile(configPath, []byte("version: 3\n"), 0o600); err != nil {
|
if err := os.WriteFile(configPath, []byte("version: 4\n"), 0o600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(filepath.Join(configDir, "required.txt"), []byte("config reference"), 0o600); err != nil {
|
if err := os.WriteFile(filepath.Join(configDir, "required.txt"), []byte("config reference"), 0o600); err != nil {
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ func runPipelineCommand(args []string, stdout, stderr io.Writer, opts Options) i
|
|||||||
return failPipelineCommand(stderr, commandState, terminalWriter, err)
|
return failPipelineCommand(stderr, commandState, terminalWriter, err)
|
||||||
}
|
}
|
||||||
profileIDs := effectiveLLMProfileIDs(effective.ResolvedPipeline)
|
profileIDs := effectiveLLMProfileIDs(effective.ResolvedPipeline)
|
||||||
if err := validateExplicitScriptoriumProfiles(context.Background(), effective.Config, profileIDs); err != nil {
|
if err := validateExplicitPromptKitProfiles(context.Background(), effective.Config, profileIDs); err != nil {
|
||||||
return failPipelineCommand(stderr, commandState, terminalWriter, err)
|
return failPipelineCommand(stderr, commandState, terminalWriter, err)
|
||||||
}
|
}
|
||||||
workingDir, err := os.Getwd()
|
workingDir, err := os.Getwd()
|
||||||
@@ -979,7 +979,7 @@ func runConfigValidate(args []string, stdout, stderr io.Writer, opts Options) in
|
|||||||
fmt.Fprintf(stderr, "notarius: %v\n", err)
|
fmt.Fprintf(stderr, "notarius: %v\n", err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
if err := validateExplicitScriptoriumProfiles(context.Background(), effective.Config, effectiveLLMProfileIDs(effective.ResolvedPipeline)); err != nil {
|
if err := validateExplicitPromptKitProfiles(context.Background(), effective.Config, effectiveLLMProfileIDs(effective.ResolvedPipeline)); err != nil {
|
||||||
fmt.Fprintf(stderr, "notarius: %v\n", err)
|
fmt.Fprintf(stderr, "notarius: %v\n", err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ func TestRunLLMProfileOverrideAndValidationUseInjectedBoundaries(t *testing.T) {
|
|||||||
t.Run("one effective profile reaches the factory and modules", func(t *testing.T) {
|
t.Run("one effective profile reaches the factory and modules", func(t *testing.T) {
|
||||||
roots := newStateTestRoots(t)
|
roots := newStateTestRoots(t)
|
||||||
profileDir := writeRunContractProfiles(t, "override-profile")
|
profileDir := writeRunContractProfiles(t, "override-profile")
|
||||||
prependRunContractConfig(t, roots, fmt.Sprintf("scriptorium:\n profile_dir: %q\n", profileDir))
|
prependRunContractConfig(t, roots, fmt.Sprintf("promptkit:\n profile_dir: %q\n", profileDir))
|
||||||
harness := newStateTestHarness()
|
harness := newStateTestHarness()
|
||||||
var factoryProfiles []string
|
var factoryProfiles []string
|
||||||
opts := harness.options()
|
opts := harness.options()
|
||||||
@@ -273,7 +273,7 @@ func TestRunLLMProfileOverrideAndValidationUseInjectedBoundaries(t *testing.T) {
|
|||||||
t.Run("validator profile remains distinct", func(t *testing.T) {
|
t.Run("validator profile remains distinct", func(t *testing.T) {
|
||||||
roots := newStateTestRoots(t)
|
roots := newStateTestRoots(t)
|
||||||
profileDir := writeRunContractProfiles(t, "override-profile", "validator-profile")
|
profileDir := writeRunContractProfiles(t, "override-profile", "validator-profile")
|
||||||
prependRunContractConfig(t, roots, fmt.Sprintf("scriptorium:\n profile_dir: %q\n", profileDir))
|
prependRunContractConfig(t, roots, fmt.Sprintf("promptkit:\n profile_dir: %q\n", profileDir))
|
||||||
harness := newStateTestHarness()
|
harness := newStateTestHarness()
|
||||||
var validatorProfiles []string
|
var validatorProfiles []string
|
||||||
opts := harness.options()
|
opts := harness.options()
|
||||||
@@ -299,7 +299,7 @@ func TestRunLLMProfileOverrideAndValidationUseInjectedBoundaries(t *testing.T) {
|
|||||||
t.Run("unknown profile is rejected without factory access", func(t *testing.T) {
|
t.Run("unknown profile is rejected without factory access", func(t *testing.T) {
|
||||||
roots := newStateTestRoots(t)
|
roots := newStateTestRoots(t)
|
||||||
profileDir := writeRunContractProfiles(t, "override-profile")
|
profileDir := writeRunContractProfiles(t, "override-profile")
|
||||||
prependRunContractConfig(t, roots, fmt.Sprintf("scriptorium:\n profile_dir: %q\n", profileDir))
|
prependRunContractConfig(t, roots, fmt.Sprintf("promptkit:\n profile_dir: %q\n", profileDir))
|
||||||
factoryCalls := 0
|
factoryCalls := 0
|
||||||
opts := newStateTestHarness().options()
|
opts := newStateTestHarness().options()
|
||||||
opts.LLMClientFactory = func(context.Context, config.Config, string) (contracts.StructuredLLMClient, []artifacts.LLMProfileManifest, error) {
|
opts.LLMClientFactory = func(context.Context, config.Config, string) (contracts.StructuredLLMClient, []artifacts.LLMProfileManifest, error) {
|
||||||
|
|||||||
@@ -635,7 +635,7 @@ func newStateTestRoots(t *testing.T) stateTestRoots {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
roots.config = filepath.Join(base, "config.yml")
|
roots.config = filepath.Join(base, "config.yml")
|
||||||
config := fmt.Sprintf("version: 3\noutput:\n directory: %q\ncache:\n chunk_plans:\n directory: %q\n mode: auto\n checkpoints:\n enabled: true\n directory: %q\ndebug:\n directory: %q\npipelines:\n sample:\n input: test/input\n chunk: test/chunk\n artifacts:\n items:\n extract: test/extract\n merge: test/merge\n normalize: test/normalize\n output: test/output\n", roots.output, roots.plans, roots.checkpoints, roots.debug)
|
config := fmt.Sprintf("version: 4\noutput:\n directory: %q\ncache:\n chunk_plans:\n directory: %q\n mode: auto\n checkpoints:\n enabled: true\n directory: %q\ndebug:\n directory: %q\npipelines:\n sample:\n input: test/input\n chunk: test/chunk\n artifacts:\n items:\n extract: test/extract\n merge: test/merge\n normalize: test/normalize\n output: test/output\n", roots.output, roots.plans, roots.checkpoints, roots.debug)
|
||||||
if err := os.WriteFile(roots.config, []byte(config), 0o600); err != nil {
|
if err := os.WriteFile(roots.config, []byte(config), 0o600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||||
)
|
)
|
||||||
|
|
||||||
const SupportedFileConfigVersion = 3
|
const SupportedFileConfigVersion = 4
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Scriptorium ScriptoriumConfig `json:"scriptorium,omitempty"`
|
PromptKit PromptKitConfig `json:"promptkit,omitempty"`
|
||||||
Pipelines map[string]pipeline.PipelineProfile `json:"pipelines"`
|
Pipelines map[string]pipeline.PipelineProfile `json:"pipelines"`
|
||||||
Concurrency ConcurrencyConfig `json:"concurrency"`
|
Concurrency ConcurrencyConfig `json:"concurrency"`
|
||||||
Output OutputConfig `json:"output"`
|
Output OutputConfig `json:"output"`
|
||||||
@@ -15,7 +15,7 @@ type Config struct {
|
|||||||
Debug DebugConfig `json:"debug"`
|
Debug DebugConfig `json:"debug"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScriptoriumConfig struct {
|
type PromptKitConfig struct {
|
||||||
ProfileDir string `json:"profile_dir,omitempty"`
|
ProfileDir string `json:"profile_dir,omitempty"`
|
||||||
ProfileFile string `json:"profile_file,omitempty"`
|
ProfileFile string `json:"profile_file,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,29 @@ func TestEffectiveConfigMaterializesDefaultBindingsThroughCatalog(t *testing.T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEffectiveConfigPreservesPromptKitProfileSource(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
profileSource PromptKitConfig
|
||||||
|
}{
|
||||||
|
{name: "profile directory", profileSource: PromptKitConfig{ProfileDir: "./profiles"}},
|
||||||
|
{name: "profile file", profileSource: PromptKitConfig{ProfileFile: "./profiles.yml"}},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cfg := configForEffectiveTests(t, effectiveProfile())
|
||||||
|
cfg.PromptKit = tt.profileSource
|
||||||
|
effective, err := cfg.Resolve(ResolveInput{PipelineID: "main", Catalog: effectiveCatalog(t)})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Resolve() error = %v", err)
|
||||||
|
}
|
||||||
|
if effective.Config.PromptKit != cfg.PromptKit {
|
||||||
|
t.Fatalf("effective PromptKit config = %#v, want %#v", effective.Config.PromptKit, cfg.PromptKit)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEffectiveConfigResolutionFailuresRetainContext(t *testing.T) {
|
func TestEffectiveConfigResolutionFailuresRetainContext(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestPrecedenceFileValuesOverrideBuiltInDefaults(t *testing.T) {
|
func TestPrecedenceFileValuesOverrideBuiltInDefaults(t *testing.T) {
|
||||||
cfg := applyFileConfig(t, `version: 3
|
cfg := applyFileConfig(t, `version: 4
|
||||||
concurrency:
|
concurrency:
|
||||||
total_llm: 4
|
total_llm: 4
|
||||||
stage_workers:
|
stage_workers:
|
||||||
@@ -35,7 +35,7 @@ debug:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPrecedenceOperationalEnvironmentOverridesFileValues(t *testing.T) {
|
func TestPrecedenceOperationalEnvironmentOverridesFileValues(t *testing.T) {
|
||||||
cfg := applyFileConfig(t, `version: 3
|
cfg := applyFileConfig(t, `version: 4
|
||||||
concurrency:
|
concurrency:
|
||||||
total_llm: 2
|
total_llm: 2
|
||||||
stage_workers:
|
stage_workers:
|
||||||
@@ -81,21 +81,21 @@ func TestPrecedenceExtractWorkersFollowEffectiveConcurrencyUnlessExplicit(t *tes
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "default follows environment total",
|
name: "default follows environment total",
|
||||||
file: "version: 3\n",
|
file: "version: 4\n",
|
||||||
env: map[string]string{"NOTARIUS_TOTAL_LLM_CONCURRENCY": "5"},
|
env: map[string]string{"NOTARIUS_TOTAL_LLM_CONCURRENCY": "5"},
|
||||||
wantTotal: 5,
|
wantTotal: 5,
|
||||||
wantWorker: 5,
|
wantWorker: 5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "file worker is retained",
|
name: "file worker is retained",
|
||||||
file: "version: 3\nconcurrency:\n total_llm: 3\n stage_workers:\n extract: 2\n",
|
file: "version: 4\nconcurrency:\n total_llm: 3\n stage_workers:\n extract: 2\n",
|
||||||
env: map[string]string{"NOTARIUS_TOTAL_LLM_CONCURRENCY": "6"},
|
env: map[string]string{"NOTARIUS_TOTAL_LLM_CONCURRENCY": "6"},
|
||||||
wantTotal: 6,
|
wantTotal: 6,
|
||||||
wantWorker: 2,
|
wantWorker: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "environment worker is retained",
|
name: "environment worker is retained",
|
||||||
file: "version: 3\nconcurrency:\n total_llm: 2\n",
|
file: "version: 4\nconcurrency:\n total_llm: 2\n",
|
||||||
env: map[string]string{
|
env: map[string]string{
|
||||||
"NOTARIUS_TOTAL_LLM_CONCURRENCY": "6",
|
"NOTARIUS_TOTAL_LLM_CONCURRENCY": "6",
|
||||||
"NOTARIUS_STAGE_WORKERS_EXTRACT": "4",
|
"NOTARIUS_STAGE_WORKERS_EXTRACT": "4",
|
||||||
@@ -118,7 +118,7 @@ func TestPrecedenceExtractWorkersFollowEffectiveConcurrencyUnlessExplicit(t *tes
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPrecedenceEmptyFileCacheDirectoriesDeferPerUserResolution(t *testing.T) {
|
func TestPrecedenceEmptyFileCacheDirectoriesDeferPerUserResolution(t *testing.T) {
|
||||||
cfg := applyFileConfig(t, `version: 3
|
cfg := applyFileConfig(t, `version: 4
|
||||||
cache:
|
cache:
|
||||||
chunk_plans:
|
chunk_plans:
|
||||||
directory: ""
|
directory: ""
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
type FileConfig struct {
|
type FileConfig struct {
|
||||||
Version int `yaml:"version"`
|
Version int `yaml:"version"`
|
||||||
Scriptorium *FileScriptoriumConfig `yaml:"scriptorium,omitempty"`
|
PromptKit *FilePromptKitConfig `yaml:"promptkit,omitempty"`
|
||||||
Pipelines map[string]FilePipelineProfile `yaml:"pipelines,omitempty"`
|
Pipelines map[string]FilePipelineProfile `yaml:"pipelines,omitempty"`
|
||||||
Concurrency *FileConcurrencyConfig `yaml:"concurrency,omitempty"`
|
Concurrency *FileConcurrencyConfig `yaml:"concurrency,omitempty"`
|
||||||
Output *FileOutputConfig `yaml:"output,omitempty"`
|
Output *FileOutputConfig `yaml:"output,omitempty"`
|
||||||
@@ -22,7 +22,7 @@ type FileConfig struct {
|
|||||||
Debug *FileDebugConfig `yaml:"debug,omitempty"`
|
Debug *FileDebugConfig `yaml:"debug,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileScriptoriumConfig struct {
|
type FilePromptKitConfig struct {
|
||||||
ProfileDir *string `yaml:"profile_dir,omitempty"`
|
ProfileDir *string `yaml:"profile_dir,omitempty"`
|
||||||
ProfileFile *string `yaml:"profile_file,omitempty"`
|
ProfileFile *string `yaml:"profile_file,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -325,6 +325,9 @@ func ParseFileConfigYAML(data []byte) (FileConfig, error) {
|
|||||||
if header.Version == 2 {
|
if header.Version == 2 {
|
||||||
return FileConfig{}, fmt.Errorf("config version 2 is no longer supported; migrate the file using the version 2-to-3 migration in docs/config.md")
|
return FileConfig{}, fmt.Errorf("config version 2 is no longer supported; migrate the file using the version 2-to-3 migration in docs/config.md")
|
||||||
}
|
}
|
||||||
|
if header.Version == 3 {
|
||||||
|
return FileConfig{}, fmt.Errorf("config version 3 is no longer supported; change \"version: 3\" to \"version: 4\" and rename \"scriptorium:\" to \"promptkit:\"")
|
||||||
|
}
|
||||||
if header.Version != SupportedFileConfigVersion {
|
if header.Version != SupportedFileConfigVersion {
|
||||||
return FileConfig{}, fmt.Errorf("unsupported config version %d (supported version is %d)", header.Version, SupportedFileConfigVersion)
|
return FileConfig{}, fmt.Errorf("unsupported config version %d (supported version is %d)", header.Version, SupportedFileConfigVersion)
|
||||||
}
|
}
|
||||||
@@ -450,20 +453,20 @@ func (c *Config) applyFileConfigWithLookup(fileCfg FileConfig, lookup func(strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fileCfg.Scriptorium != nil {
|
if fileCfg.PromptKit != nil {
|
||||||
if fileCfg.Scriptorium.ProfileDir != nil {
|
if fileCfg.PromptKit.ProfileDir != nil {
|
||||||
value := strings.TrimSpace(*fileCfg.Scriptorium.ProfileDir)
|
value := strings.TrimSpace(*fileCfg.PromptKit.ProfileDir)
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return fmt.Errorf("scriptorium.profile_dir must not be empty when set")
|
return fmt.Errorf("promptkit.profile_dir must not be empty when set")
|
||||||
}
|
}
|
||||||
c.Scriptorium.ProfileDir = value
|
c.PromptKit.ProfileDir = value
|
||||||
}
|
}
|
||||||
if fileCfg.Scriptorium.ProfileFile != nil {
|
if fileCfg.PromptKit.ProfileFile != nil {
|
||||||
value := strings.TrimSpace(*fileCfg.Scriptorium.ProfileFile)
|
value := strings.TrimSpace(*fileCfg.PromptKit.ProfileFile)
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return fmt.Errorf("scriptorium.profile_file must not be empty when set")
|
return fmt.Errorf("promptkit.profile_file must not be empty when set")
|
||||||
}
|
}
|
||||||
c.Scriptorium.ProfileFile = value
|
c.PromptKit.ProfileFile = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -34,8 +35,8 @@ func TestDefaultReturnsDocumentedValuesAndIndependentMaps(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileConfigMinimalVersion3AppliesOverDefaults(t *testing.T) {
|
func TestFileConfigMinimalVersion4AppliesOverDefaults(t *testing.T) {
|
||||||
file := parseFileConfig(t, "version: 3\n")
|
file := parseFileConfig(t, "version: 4\n")
|
||||||
cfg := Default()
|
cfg := Default()
|
||||||
if err := cfg.ApplyFileConfig(file); err != nil {
|
if err := cfg.ApplyFileConfig(file); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -48,6 +49,78 @@ func TestFileConfigMinimalVersion3AppliesOverDefaults(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFilePromptKitProfileSourcesSurviveConfigBoundaries(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
yaml string
|
||||||
|
want PromptKitConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "profile directory",
|
||||||
|
yaml: "version: 4\npromptkit:\n profile_dir: ' ./profiles '\n",
|
||||||
|
want: PromptKitConfig{ProfileDir: "./profiles"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "profile file",
|
||||||
|
yaml: "version: 4\npromptkit:\n profile_file: ' ./profiles.yml '\n",
|
||||||
|
want: PromptKitConfig{ProfileFile: "./profiles.yml"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cfg := applyFileConfig(t, tt.yaml)
|
||||||
|
if cfg.PromptKit != tt.want {
|
||||||
|
t.Fatalf("PromptKit config = %#v, want %#v", cfg.PromptKit, tt.want)
|
||||||
|
}
|
||||||
|
if got := cloneConfig(cfg).PromptKit; got != tt.want {
|
||||||
|
t.Fatalf("cloned PromptKit config = %#v, want %#v", got, tt.want)
|
||||||
|
}
|
||||||
|
if got := cfg.Redacted().PromptKit; got != tt.want {
|
||||||
|
t.Fatalf("redacted PromptKit config = %#v, want %#v", got, tt.want)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("json.Marshal() error = %v", err)
|
||||||
|
}
|
||||||
|
var payload map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(data, &payload); err != nil {
|
||||||
|
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||||
|
}
|
||||||
|
if _, ok := payload["promptkit"]; !ok {
|
||||||
|
t.Fatalf("runtime JSON keys = %v, want promptkit", payload)
|
||||||
|
}
|
||||||
|
if _, ok := payload["scriptorium"]; ok {
|
||||||
|
t.Fatalf("runtime JSON keys = %v, must not contain removed section", payload)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilePromptKitExplicitEmptyProfileSourcesAreRejected(t *testing.T) {
|
||||||
|
for _, field := range []string{"profile_dir", "profile_file"} {
|
||||||
|
t.Run(field, func(t *testing.T) {
|
||||||
|
file := parseFileConfig(t, "version: 4\npromptkit:\n "+field+": ''\n")
|
||||||
|
cfg := Default()
|
||||||
|
err := cfg.ApplyFileConfig(file)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "promptkit."+field+" must not be empty") {
|
||||||
|
t.Fatalf("ApplyFileConfig() error = %v, want explicit-empty rejection", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilePromptKitProfileSourcesRemainMutuallyExclusive(t *testing.T) {
|
||||||
|
cfg := applyFileConfig(t, `version: 4
|
||||||
|
promptkit:
|
||||||
|
profile_dir: ./profiles
|
||||||
|
profile_file: ./profiles.yml
|
||||||
|
`)
|
||||||
|
if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "promptkit profile_dir and profile_file are mutually exclusive") {
|
||||||
|
t.Fatalf("Validate() error = %v, want mutually exclusive profile sources", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFileConfigMissingVersionIsReportedBeforeFieldDecoding(t *testing.T) {
|
func TestFileConfigMissingVersionIsReportedBeforeFieldDecoding(t *testing.T) {
|
||||||
_, err := ParseFileConfigYAML([]byte("workspace:\n directory: /tmp/old\n"))
|
_, err := ParseFileConfigYAML([]byte("workspace:\n directory: /tmp/old\n"))
|
||||||
if err == nil || !strings.Contains(err.Error(), "config version is required") {
|
if err == nil || !strings.Contains(err.Error(), "config version is required") {
|
||||||
@@ -55,6 +128,15 @@ func TestFileConfigMissingVersionIsReportedBeforeFieldDecoding(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFileConfigVersion3ReportsPromptKitMigration(t *testing.T) {
|
||||||
|
_, err := ParseFileConfigYAML([]byte("version: 3\nscriptorium:\n profile_dir: ./profiles\n"))
|
||||||
|
if err == nil ||
|
||||||
|
!strings.Contains(err.Error(), `change "version: 3" to "version: 4"`) ||
|
||||||
|
!strings.Contains(err.Error(), `rename "scriptorium:" to "promptkit:"`) {
|
||||||
|
t.Fatalf("version 3 error = %v, want actionable version and section migration", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFileConfigRejectsUnknownCurrentAndRemovedFields(t *testing.T) {
|
func TestFileConfigRejectsUnknownCurrentAndRemovedFields(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -63,14 +145,19 @@ func TestFileConfigRejectsUnknownCurrentAndRemovedFields(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "removed diagnostics",
|
name: "removed diagnostics",
|
||||||
yaml: "version: 3\ndiagnostics: {}\n",
|
yaml: "version: 4\ndiagnostics: {}\n",
|
||||||
want: "field diagnostics not found",
|
want: "field diagnostics not found",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "removed llm profiles",
|
name: "removed llm profiles",
|
||||||
yaml: "version: 3\nllm_profiles: {}\n",
|
yaml: "version: 4\nllm_profiles: {}\n",
|
||||||
want: "field llm_profiles not found",
|
want: "field llm_profiles not found",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "removed scriptorium section",
|
||||||
|
yaml: "version: 4\nscriptorium: {}\n",
|
||||||
|
want: "field scriptorium not found",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "version 2 migration",
|
name: "version 2 migration",
|
||||||
yaml: "version: 2\nworkspace:\n directory: /tmp/old\n",
|
yaml: "version: 2\nworkspace:\n directory: /tmp/old\n",
|
||||||
@@ -78,27 +165,27 @@ func TestFileConfigRejectsUnknownCurrentAndRemovedFields(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "pipeline field",
|
name: "pipeline field",
|
||||||
yaml: "version: 3\npipelines:\n main:\n unknown: true\n",
|
yaml: "version: 4\npipelines:\n main:\n unknown: true\n",
|
||||||
want: "field unknown not found",
|
want: "field unknown not found",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "lane field",
|
name: "lane field",
|
||||||
yaml: "version: 3\npipelines:\n main:\n artifacts:\n spells:\n unknown: true\n",
|
yaml: "version: 4\npipelines:\n main:\n artifacts:\n spells:\n unknown: true\n",
|
||||||
want: "field unknown not found",
|
want: "field unknown not found",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "module binding field",
|
name: "module binding field",
|
||||||
yaml: "version: 3\npipelines:\n main:\n input:\n module: seriatim\n unknown: true\n",
|
yaml: "version: 4\npipelines:\n main:\n input:\n module: seriatim\n unknown: true\n",
|
||||||
want: "field unknown not found in module binding",
|
want: "field unknown not found in module binding",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "checkpoint field",
|
name: "checkpoint field",
|
||||||
yaml: "version: 3\ncache:\n checkpoints:\n unknown: true\n",
|
yaml: "version: 4\ncache:\n checkpoints:\n unknown: true\n",
|
||||||
want: "field unknown not found",
|
want: "field unknown not found",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "checkpoint enabled type",
|
name: "checkpoint enabled type",
|
||||||
yaml: "version: 3\ncache:\n checkpoints:\n enabled: definitely\n",
|
yaml: "version: 4\ncache:\n checkpoints:\n enabled: definitely\n",
|
||||||
want: "cannot unmarshal",
|
want: "cannot unmarshal",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -113,7 +200,7 @@ func TestFileConfigRejectsUnknownCurrentAndRemovedFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileConfigModuleBindingsPreserveFormsAndValidatorPresence(t *testing.T) {
|
func TestFileConfigModuleBindingsPreserveFormsAndValidatorPresence(t *testing.T) {
|
||||||
cfg := applyFileConfig(t, `version: 3
|
cfg := applyFileConfig(t, `version: 4
|
||||||
pipelines:
|
pipelines:
|
||||||
main:
|
main:
|
||||||
input: seriatim
|
input: seriatim
|
||||||
@@ -160,7 +247,7 @@ pipelines:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileConfigReferencePrecedenceIsRetained(t *testing.T) {
|
func TestFileConfigReferencePrecedenceIsRetained(t *testing.T) {
|
||||||
cfg := applyFileConfig(t, `version: 3
|
cfg := applyFileConfig(t, `version: 4
|
||||||
pipelines:
|
pipelines:
|
||||||
main:
|
main:
|
||||||
input: seriatim
|
input: seriatim
|
||||||
@@ -224,7 +311,7 @@ pipelines:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileConfigStageLocalValidatorsPreserveOrderAndFields(t *testing.T) {
|
func TestFileConfigStageLocalValidatorsPreserveOrderAndFields(t *testing.T) {
|
||||||
cfg := applyFileConfig(t, `version: 3
|
cfg := applyFileConfig(t, `version: 4
|
||||||
pipelines:
|
pipelines:
|
||||||
main:
|
main:
|
||||||
input: seriatim
|
input: seriatim
|
||||||
@@ -277,8 +364,8 @@ pipelines:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileConfigStateSectionsApplyIndependently(t *testing.T) {
|
func TestFileConfigStateSectionsApplyIndependently(t *testing.T) {
|
||||||
cfg := applyFileConfig(t, `version: 3
|
cfg := applyFileConfig(t, `version: 4
|
||||||
scriptorium:
|
promptkit:
|
||||||
profile_dir: ./profiles
|
profile_dir: ./profiles
|
||||||
concurrency:
|
concurrency:
|
||||||
total_llm: 7
|
total_llm: 7
|
||||||
@@ -294,15 +381,15 @@ cache:
|
|||||||
debug:
|
debug:
|
||||||
directory: ./debug
|
directory: ./debug
|
||||||
`)
|
`)
|
||||||
if cfg.Scriptorium.ProfileDir != "./profiles" || cfg.Scriptorium.ProfileFile != "" {
|
if cfg.PromptKit.ProfileDir != "./profiles" || cfg.PromptKit.ProfileFile != "" {
|
||||||
t.Fatalf("scriptorium = %#v", cfg.Scriptorium)
|
t.Fatalf("promptkit = %#v", cfg.PromptKit)
|
||||||
}
|
}
|
||||||
if cfg.Concurrency.TotalLLM != 7 || cfg.Concurrency.StageWorkers["extract"] != 7 {
|
if cfg.Concurrency.TotalLLM != 7 || cfg.Concurrency.StageWorkers["extract"] != 7 {
|
||||||
t.Fatalf("concurrency = %#v", cfg.Concurrency)
|
t.Fatalf("concurrency = %#v", cfg.Concurrency)
|
||||||
}
|
}
|
||||||
if cfg.Output.Directory != "./output" || cfg.Cache.ChunkPlans.Directory != "plans" || cfg.Cache.ChunkPlans.Mode != pipeline.ChunkCacheBypass ||
|
if cfg.Output.Directory != "./output" || cfg.Cache.ChunkPlans.Directory != "plans" || cfg.Cache.ChunkPlans.Mode != pipeline.ChunkCacheBypass ||
|
||||||
!cfg.Cache.Checkpoints.Enabled || cfg.Cache.Checkpoints.Directory != "checkpoints" || cfg.Debug.Directory != "./debug" {
|
!cfg.Cache.Checkpoints.Enabled || cfg.Cache.Checkpoints.Directory != "checkpoints" || cfg.Debug.Directory != "./debug" {
|
||||||
t.Fatalf("state sections = %#v, %#v, %#v, %#v", cfg.Output, cfg.Cache, cfg.Debug, cfg.Scriptorium)
|
t.Fatalf("state sections = %#v, %#v, %#v, %#v", cfg.Output, cfg.Cache, cfg.Debug, cfg.PromptKit)
|
||||||
}
|
}
|
||||||
if cfg.Output.Directory == cfg.Cache.ChunkPlans.Directory || cfg.Cache.ChunkPlans.Directory == cfg.Cache.Checkpoints.Directory || cfg.Cache.Checkpoints.Directory == cfg.Debug.Directory {
|
if cfg.Output.Directory == cfg.Cache.ChunkPlans.Directory || cfg.Cache.ChunkPlans.Directory == cfg.Cache.Checkpoints.Directory || cfg.Cache.Checkpoints.Directory == cfg.Debug.Directory {
|
||||||
t.Fatal("state roots were coupled")
|
t.Fatal("state roots were coupled")
|
||||||
@@ -310,11 +397,11 @@ debug:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileConfigCheckpointEnabledCanBeExplicitlyDisabled(t *testing.T) {
|
func TestFileConfigCheckpointEnabledCanBeExplicitlyDisabled(t *testing.T) {
|
||||||
cfg := applyFileConfig(t, "version: 3\ncache:\n checkpoints:\n enabled: true\n")
|
cfg := applyFileConfig(t, "version: 4\ncache:\n checkpoints:\n enabled: true\n")
|
||||||
if !cfg.Cache.Checkpoints.Enabled || !cloneConfig(cfg).Cache.Checkpoints.Enabled {
|
if !cfg.Cache.Checkpoints.Enabled || !cloneConfig(cfg).Cache.Checkpoints.Enabled {
|
||||||
t.Fatalf("enabled checkpoint config was not retained: %#v", cfg.Cache.Checkpoints)
|
t.Fatalf("enabled checkpoint config was not retained: %#v", cfg.Cache.Checkpoints)
|
||||||
}
|
}
|
||||||
file := parseFileConfig(t, "version: 3\ncache:\n checkpoints:\n enabled: false\n")
|
file := parseFileConfig(t, "version: 4\ncache:\n checkpoints:\n enabled: false\n")
|
||||||
if err := cfg.ApplyFileConfig(file); err != nil {
|
if err := cfg.ApplyFileConfig(file); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -331,17 +418,17 @@ func TestFileConfigRejectsTrimmedKeyCollisions(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "pipeline ids",
|
name: "pipeline ids",
|
||||||
yaml: "version: 3\npipelines:\n main: {}\n ' main ': {}\n",
|
yaml: "version: 4\npipelines:\n main: {}\n ' main ': {}\n",
|
||||||
want: "pipeline id \"main\" is duplicated after trimming",
|
want: "pipeline id \"main\" is duplicated after trimming",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "lane ids",
|
name: "lane ids",
|
||||||
yaml: "version: 3\npipelines:\n main:\n artifacts:\n spells: {}\n ' spells ': {}\n",
|
yaml: "version: 4\npipelines:\n main:\n artifacts:\n spells: {}\n ' spells ': {}\n",
|
||||||
want: "artifact lane id \"spells\" is duplicated after trimming",
|
want: "artifact lane id \"spells\" is duplicated after trimming",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "reference slots",
|
name: "reference slots",
|
||||||
yaml: "version: 3\npipelines:\n main:\n references:\n slot: ./one.txt\n ' slot ': ./two.txt\n",
|
yaml: "version: 4\npipelines:\n main:\n references:\n slot: ./one.txt\n ' slot ': ./two.txt\n",
|
||||||
want: "reference slot \"slot\" is duplicated after trimming",
|
want: "reference slot \"slot\" is duplicated after trimming",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -358,7 +445,7 @@ func TestFileConfigRejectsTrimmedKeyCollisions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileConfigParsesOrderedStepsAndReferenceSources(t *testing.T) {
|
func TestFileConfigParsesOrderedStepsAndReferenceSources(t *testing.T) {
|
||||||
file := parseFileConfig(t, `version: 3
|
file := parseFileConfig(t, `version: 4
|
||||||
pipelines:
|
pipelines:
|
||||||
session:
|
session:
|
||||||
input: seriatim
|
input: seriatim
|
||||||
@@ -397,7 +484,7 @@ func TestFileConfigRejectsAmbiguousReferenceSourceForms(t *testing.T) {
|
|||||||
"artifact: {step: 1, lane: b}",
|
"artifact: {step: 1, lane: b}",
|
||||||
"1",
|
"1",
|
||||||
} {
|
} {
|
||||||
_, err := ParseFileConfigYAML([]byte("version: 3\npipelines:\n p:\n input: text\n references:\n slot: " + source + "\n"))
|
_, err := ParseFileConfigYAML([]byte("version: 4\npipelines:\n p:\n input: text\n references:\n slot: " + source + "\n"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("ParseFileConfigYAML(%q) error = nil", source)
|
t.Fatalf("ParseFileConfigYAML(%q) error = nil", source)
|
||||||
}
|
}
|
||||||
@@ -412,12 +499,12 @@ func TestFileConfigRejectsEmptyAndAmbiguousPipelineShapes(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "empty steps",
|
name: "empty steps",
|
||||||
yaml: "version: 3\npipelines:\n p:\n input: text\n steps: []\n",
|
yaml: "version: 4\npipelines:\n p:\n input: text\n steps: []\n",
|
||||||
want: "at least one ordered step",
|
want: "at least one ordered step",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "both forms",
|
name: "both forms",
|
||||||
yaml: "version: 3\npipelines:\n p:\n input: text\n artifacts: {}\n steps: []\n",
|
yaml: "version: 4\npipelines:\n p:\n input: text\n artifacts: {}\n steps: []\n",
|
||||||
want: "both artifacts and steps",
|
want: "both artifacts and steps",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
func (c Config) Validate() error {
|
func (c Config) Validate() error {
|
||||||
c.Concurrency.recomputeStageWorkerDefaults()
|
c.Concurrency.recomputeStageWorkerDefaults()
|
||||||
if err := validateScriptorium(c.Scriptorium); err != nil {
|
if err := validatePromptKit(c.PromptKit); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := validateStateSurfaces(c); err != nil {
|
if err := validateStateSurfaces(c); err != nil {
|
||||||
@@ -49,9 +49,9 @@ func validateStageWorkers(cfg ConcurrencyConfig) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateScriptorium(cfg ScriptoriumConfig) error {
|
func validatePromptKit(cfg PromptKitConfig) error {
|
||||||
if strings.TrimSpace(cfg.ProfileDir) != "" && strings.TrimSpace(cfg.ProfileFile) != "" {
|
if strings.TrimSpace(cfg.ProfileDir) != "" && strings.TrimSpace(cfg.ProfileFile) != "" {
|
||||||
return fmt.Errorf("scriptorium profile_dir and profile_file are mutually exclusive")
|
return fmt.Errorf("promptkit profile_dir and profile_file are mutually exclusive")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,10 +87,10 @@ func TestValidateConcurrencyRules(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateScriptoriumSourcesAreMutuallyExclusive(t *testing.T) {
|
func TestValidatePromptKitSourcesAreMutuallyExclusive(t *testing.T) {
|
||||||
cfg := Default()
|
cfg := Default()
|
||||||
cfg.Scriptorium = ScriptoriumConfig{ProfileDir: "./profiles", ProfileFile: "./profile.yml"}
|
cfg.PromptKit = PromptKitConfig{ProfileDir: "./profiles", ProfileFile: "./profile.yml"}
|
||||||
assertValidationContains(t, cfg, "scriptorium profile_dir and profile_file are mutually exclusive")
|
assertValidationContains(t, cfg, "promptkit profile_dir and profile_file are mutually exclusive")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateStateSurfaceRules(t *testing.T) {
|
func TestValidateStateSurfaceRules(t *testing.T) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
version: 3
|
version: 4
|
||||||
output:
|
output:
|
||||||
directory: ./notarius-output
|
directory: ./notarius-output
|
||||||
cache:
|
cache:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
version: 3
|
version: 4
|
||||||
output:
|
output:
|
||||||
directory: ./notarius-output
|
directory: ./notarius-output
|
||||||
cache:
|
cache:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
version: 3
|
version: 4
|
||||||
output:
|
output:
|
||||||
directory: ./notarius-output
|
directory: ./notarius-output
|
||||||
cache:
|
cache:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
version: 3
|
version: 4
|
||||||
output:
|
output:
|
||||||
directory: ./notarius-output
|
directory: ./notarius-output
|
||||||
cache:
|
cache:
|
||||||
|
|||||||
Reference in New Issue
Block a user