Add integrated sequence catalog examples
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Sequence Identity And Combination Implementation Plan
|
||||
|
||||
Status: implementation in progress; Stages 1–27 complete.
|
||||
Status: implementation in progress; Stages 1–28 complete.
|
||||
|
||||
Audience: a Luna coding agent implementing one numbered stage per prompt, in
|
||||
order. This plan starts at commit
|
||||
@@ -948,6 +948,23 @@ Scope: a small number of meaningful integrations, not a Cartesian test matrix.
|
||||
Validation: `go test ./examples/consumers`, focused root integration tests,
|
||||
`go test -race ./...`, and the everyday check.
|
||||
|
||||
**Completion:** Added an executable source-backed catalog that combines an
|
||||
underlay identity declaration, imported main fragments, identified record
|
||||
inheritance, all three selected overlay operations, typed CLI assignment, and
|
||||
recorded application resolution. Its checks demonstrate stable operation order,
|
||||
complete matched-record replacement, final metadata and history inspection,
|
||||
inherited resource lookup through source context, and secret-safe explanation.
|
||||
Added a typed-map example with exact and wildcard registrations, independent
|
||||
uniqueness across concrete collections, and a YAML-only identity example that
|
||||
shows the absence of final application enforcement without registration. Audited
|
||||
the existing helper-composed parent reference, source-version control consumption,
|
||||
pending declarations, lower-tier identity before peer composition, and policy
|
||||
replacement ownership coverage; added a consumer regression for a dynamic
|
||||
collection destination introduced by inheritance. Verified
|
||||
`go test ./examples/consumers -count=1`,
|
||||
`go test . -run 'Facade.*(Inheritance|Sequence|Override)' -count=1`,
|
||||
`go test -race ./...`, and `./scripts/check.sh`.
|
||||
|
||||
## Stage 29 — Reconcile Documentation
|
||||
|
||||
Scope: finish the current-contract narrative. Code cleanup belongs with the
|
||||
|
||||
134
examples/consumers/sequence_catalog.go
Normal file
134
examples/consumers/sequence_catalog.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package consumers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
||||
)
|
||||
|
||||
// SequenceCatalogConfig is a source-backed prompt catalog with an application
|
||||
// supplied run label and a value derived from that label during resolution.
|
||||
type SequenceCatalogConfig struct {
|
||||
RunLabel string `yaml:"run_label"`
|
||||
ResolvedLabel string `yaml:"resolved_label"`
|
||||
Prompts []SequenceCatalogEntry `yaml:"prompts"`
|
||||
}
|
||||
|
||||
// SequenceCatalogEntry keeps prompt meaning, resources, and credentials in the
|
||||
// consuming application while ConfigKit handles collection assembly.
|
||||
type SequenceCatalogEntry struct {
|
||||
ID string `yaml:"id"`
|
||||
Name string `yaml:"name"`
|
||||
Description string `yaml:"description"`
|
||||
Messages []string `yaml:"messages"`
|
||||
Resource string `yaml:"resource"`
|
||||
Credential configkit.Secret `yaml:"credential"`
|
||||
}
|
||||
|
||||
// LoadSequenceCatalog assembles imported main fragments over a file-backed
|
||||
// underlay, applies one selected collection overlay, then resolves and validates
|
||||
// the resulting application configuration.
|
||||
func LoadSequenceCatalog(mainPath, overlayName, runLabel string) (*configkit.Result[SequenceCatalogConfig], error) {
|
||||
root := filepath.Dir(mainPath)
|
||||
filesystem := configkit.NewFilesystem("sequence catalog", os.DirFS(root))
|
||||
filesystemContext := &configkit.FilesystemContext{BaseDir: ".", Root: ".", FS: &filesystem}
|
||||
definition := configkit.Definition[SequenceCatalogConfig]{
|
||||
SequenceIdentities: []configkit.SequenceIdentity{
|
||||
configkit.SequenceIdentityAt(configkit.Keys("prompts"), "id"),
|
||||
},
|
||||
Resolve: func(configuration *SequenceCatalogConfig, resolution *configkit.Resolution) error {
|
||||
configuration.ResolvedLabel = "for:" + configuration.RunLabel
|
||||
resolution.Record(configkit.Keys("resolved_label"), configkit.ChangeNormalization,
|
||||
"derive the resolved label from the selected run label", configkit.Keys("run_label"))
|
||||
return nil
|
||||
},
|
||||
Validate: validateSequenceCatalog,
|
||||
}
|
||||
return configkit.LoadLayered(configkit.Input{
|
||||
Source: filesystem.File(filepath.Base(mainPath)),
|
||||
Imports: true,
|
||||
}, configkit.LayeredOptions[SequenceCatalogConfig]{
|
||||
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{
|
||||
Source: filesystem.File("underlay.yml"),
|
||||
}}}},
|
||||
Overlay: overlayName,
|
||||
OverlayFilesystem: filesystemContext,
|
||||
Overrides: []configkit.Override{{
|
||||
Path: configkit.Keys("run_label"), Value: runLabel, Flag: "--run-label",
|
||||
}},
|
||||
Definition: definition,
|
||||
})
|
||||
}
|
||||
|
||||
// ReadSequenceCatalogResource follows the final prompt field's source context,
|
||||
// including when record inheritance retained a resource from an underlay.
|
||||
func ReadSequenceCatalogResource(result *configkit.Result[SequenceCatalogConfig], promptID string) (string, error) {
|
||||
if result == nil {
|
||||
return "", errors.New("sequence catalog result is nil")
|
||||
}
|
||||
for index, prompt := range result.Config.Prompts {
|
||||
if prompt.ID != promptID {
|
||||
continue
|
||||
}
|
||||
resourcePath := configkit.Keys("prompts").Index(index).Key("resource")
|
||||
info, found := result.Metadata().Lookup(resourcePath)
|
||||
if !found || len(info.Origin.Locations) != 1 {
|
||||
return "", errors.New("prompt resource has no single declaring source")
|
||||
}
|
||||
context, found := result.SourceContext(info.Origin.Locations[0].SourceID)
|
||||
if !found {
|
||||
return "", errors.New("prompt resource source context is unavailable")
|
||||
}
|
||||
data, err := fs.ReadFile(context.FS, path.Join(context.BaseDir, prompt.Resource))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read prompt resource: %w", err)
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
return "", fmt.Errorf("prompt %q is unavailable", promptID)
|
||||
}
|
||||
|
||||
func validateSequenceCatalog(configuration *SequenceCatalogConfig, validation *configkit.Validation) {
|
||||
for index, prompt := range configuration.Prompts {
|
||||
if strings.TrimSpace(prompt.Name) == "" {
|
||||
validation.Error(configkit.Keys("prompts").Index(index).Key("name"), "must not be empty")
|
||||
}
|
||||
if len(prompt.Messages) == 0 {
|
||||
validation.Error(configkit.Keys("prompts").Index(index).Key("messages"), "must contain at least one message")
|
||||
}
|
||||
}
|
||||
if len(configuration.Prompts) == 0 {
|
||||
validation.Error(configkit.Keys("prompts"), "must contain at least one prompt")
|
||||
}
|
||||
}
|
||||
|
||||
// NamespacedMapCatalogConfig demonstrates typed application maps whose
|
||||
// independent prompt sequences use root and dynamic namespace registrations.
|
||||
type NamespacedMapCatalogConfig struct {
|
||||
Prompts []map[string]any `yaml:"prompts"`
|
||||
Collections map[string]map[string]any `yaml:"collections"`
|
||||
}
|
||||
|
||||
// LoadNamespacedMapCatalog applies the same scalar identity constraint to one
|
||||
// root collection and each namespace's independently scoped collection.
|
||||
func LoadNamespacedMapCatalog(source configkit.Source) (*configkit.Result[NamespacedMapCatalogConfig], error) {
|
||||
selector := configkit.SequenceSelector{
|
||||
configkit.SequenceLiteralKey("collections"),
|
||||
configkit.SequenceAnyMappingKey(),
|
||||
configkit.SequenceLiteralKey("prompts"),
|
||||
}
|
||||
return configkit.Load(source, configkit.Options[NamespacedMapCatalogConfig]{
|
||||
Definition: configkit.Definition[NamespacedMapCatalogConfig]{
|
||||
SequenceIdentities: []configkit.SequenceIdentity{
|
||||
configkit.SequenceIdentityAt(configkit.Keys("prompts"), "id"),
|
||||
configkit.SequenceIdentityMatching(selector, "id"),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
239
examples/consumers/sequence_catalog_test.go
Normal file
239
examples/consumers/sequence_catalog_test.go
Normal file
@@ -0,0 +1,239 @@
|
||||
package consumers_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
||||
"gitea.maximumdirect.net/eric/ConfigKit/examples/consumers"
|
||||
)
|
||||
|
||||
func Example_sequenceCatalog() {
|
||||
const oldSecret = "CONFIGKIT_SEQUENCE_CATALOG_OLD_SECRET"
|
||||
const newSecret = "CONFIGKIT_SEQUENCE_CATALOG_NEW_SECRET"
|
||||
restoreOld, restoreNew := preserveEnvironment(oldSecret), preserveEnvironment(newSecret)
|
||||
defer restoreOld()
|
||||
defer restoreNew()
|
||||
_ = os.Setenv(oldSecret, "disposable-old-example-credential")
|
||||
_ = os.Setenv(newSecret, "disposable-new-example-credential")
|
||||
|
||||
result, err := consumers.LoadSequenceCatalog(fixture("sequence-catalog", "config.yml"), "upsert", "preview")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
ids := make([]string, 0, len(result.Config.Prompts))
|
||||
for _, prompt := range result.Config.Prompts {
|
||||
ids = append(ids, prompt.ID)
|
||||
}
|
||||
shared := configkit.Keys("prompts").Index(1).Key("name")
|
||||
info, _ := result.Metadata().Lookup(shared)
|
||||
resource, resourceErr := consumers.ReadSequenceCatalogResource(result, "concise")
|
||||
fmt.Println(ids, result.Config.ResolvedLabel, info.Explicit, len(result.Metadata().History(shared)), resource, resourceErr == nil, err)
|
||||
// Output: [foundation shared welcome concise extra] for:preview true 2 Foundation resource text.
|
||||
// true <nil>
|
||||
}
|
||||
|
||||
func TestSequenceCatalogSelectedOverlayOperations(t *testing.T) {
|
||||
const oldSecret = "CONFIGKIT_SEQUENCE_CATALOG_OLD_SECRET"
|
||||
const newSecret = "CONFIGKIT_SEQUENCE_CATALOG_NEW_SECRET"
|
||||
t.Setenv(oldSecret, "sequence-catalog-old-canary")
|
||||
t.Setenv(newSecret, "sequence-catalog-new-canary")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
wantIDs []string
|
||||
shared *consumers.SequenceCatalogEntry
|
||||
}{
|
||||
{name: "append", wantIDs: []string{"foundation", "shared", "welcome", "concise", "extra"}},
|
||||
{name: "upsert", wantIDs: []string{"foundation", "shared", "welcome", "concise", "extra"}, shared: &consumers.SequenceCatalogEntry{ID: "shared", Name: "Shared replacement", Messages: []string{"Replacement message"}, Credential: configkit.SecretEnv(newSecret)}},
|
||||
{name: "replace", wantIDs: []string{"replacement", "last"}},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := consumers.LoadSequenceCatalog(fixture("sequence-catalog", "config.yml"), test.name, "test")
|
||||
if err != nil {
|
||||
var loadError *configkit.LoadError
|
||||
if errors.As(err, &loadError) {
|
||||
t.Fatalf("load selected overlay: %v (%#v)", err, loadError.Diagnostics())
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
gotIDs := make([]string, 0, len(result.Config.Prompts))
|
||||
for _, prompt := range result.Config.Prompts {
|
||||
gotIDs = append(gotIDs, prompt.ID)
|
||||
}
|
||||
if strings.Join(gotIDs, ",") != strings.Join(test.wantIDs, ",") {
|
||||
t.Fatalf("prompt order = %v, want %v", gotIDs, test.wantIDs)
|
||||
}
|
||||
if test.shared == nil {
|
||||
return
|
||||
}
|
||||
shared := result.Config.Prompts[1]
|
||||
if shared.ID != test.shared.ID || shared.Name != test.shared.Name || len(shared.Messages) != 1 ||
|
||||
shared.Messages[0] != test.shared.Messages[0] || shared.Description != "" || shared.Resource != "" || shared.Credential.EnvName() != newSecret {
|
||||
t.Fatalf("upsert did not replace the complete matched record: %#v", shared)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSequenceCatalogInspectionTracksResourcesAndSecretHistory(t *testing.T) {
|
||||
const oldSecret = "CONFIGKIT_SEQUENCE_CATALOG_OLD_SECRET"
|
||||
const newSecret = "CONFIGKIT_SEQUENCE_CATALOG_NEW_SECRET"
|
||||
const oldCanary = "sequence-catalog-old-canary"
|
||||
const newCanary = "sequence-catalog-new-canary"
|
||||
t.Setenv(oldSecret, oldCanary)
|
||||
t.Setenv(newSecret, newCanary)
|
||||
|
||||
result, err := consumers.LoadSequenceCatalog(fixture("sequence-catalog", "config.yml"), "upsert", "history")
|
||||
if err != nil {
|
||||
var loadError *configkit.LoadError
|
||||
if errors.As(err, &loadError) {
|
||||
t.Fatalf("load sequence catalog: %v (%#v)", err, loadError.Diagnostics())
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
resourcePath := configkit.Keys("prompts").Index(3).Key("resource")
|
||||
resourceInfo, found := result.Metadata().Lookup(resourcePath)
|
||||
if !found || len(resourceInfo.Origin.Locations) != 1 || len(resourceInfo.Origin.Inheritance) != 1 {
|
||||
t.Fatalf("inherited resource inspection = %#v, %v", resourceInfo, found)
|
||||
}
|
||||
resource, err := consumers.ReadSequenceCatalogResource(result, "concise")
|
||||
if err != nil || resource != "Foundation resource text.\n" {
|
||||
t.Fatalf("resource from inherited source context = %q, %v", resource, err)
|
||||
}
|
||||
if len(result.Metadata().History(configkit.Keys("resolved_label"))) == 0 {
|
||||
t.Fatal("recorded CLI-driven resolution was absent from final inspection")
|
||||
}
|
||||
credentialPath := configkit.Keys("prompts").Index(1).Key("credential")
|
||||
credentialHistory := result.Metadata().History(credentialPath)
|
||||
if len(credentialHistory) == 0 {
|
||||
t.Fatal("upsert did not retain the replaced secret reference in history")
|
||||
}
|
||||
var explanation bytes.Buffer
|
||||
if err := result.WriteExplanation(&explanation, credentialPath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(explanation.String(), oldCanary) || strings.Contains(explanation.String(), newCanary) {
|
||||
t.Fatalf("secret explanation exposed a credential value: %s", explanation.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespacedTypedMapIdentityScopesAreIndependent(t *testing.T) {
|
||||
source := configkit.Bytes("namespaced maps", []byte(`
|
||||
prompts:
|
||||
- id: shared
|
||||
text: root prompt
|
||||
collections:
|
||||
team:
|
||||
prompts:
|
||||
- id: shared
|
||||
text: team prompt
|
||||
operations:
|
||||
prompts:
|
||||
- id: shared
|
||||
text: operations prompt
|
||||
`))
|
||||
result, err := consumers.LoadNamespacedMapCatalog(source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Config.Prompts[0]["text"] != "root prompt" ||
|
||||
result.Config.Collections["team"]["prompts"].([]any)[0].(map[string]any)["text"] != "team prompt" ||
|
||||
result.Config.Collections["operations"]["prompts"].([]any)[0].(map[string]any)["text"] != "operations prompt" {
|
||||
t.Fatalf("typed map collections = %#v", result.Config)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespacedTypedMapIdentityFindsCollectionsIntroducedByInheritance(t *testing.T) {
|
||||
result, err := consumers.LoadNamespacedMapCatalog(configkit.Bytes("inherited namespace", []byte(`
|
||||
collections:
|
||||
source:
|
||||
prompts:
|
||||
- id: shared
|
||||
text: inherited prompt
|
||||
copied:
|
||||
extends: source
|
||||
`)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
copied := result.Config.Collections["copied"]["prompts"].([]any)
|
||||
if len(copied) != 1 || copied[0].(map[string]any)["text"] != "inherited prompt" {
|
||||
t.Fatalf("inherited typed-map collection = %#v", result.Config.Collections["copied"])
|
||||
}
|
||||
}
|
||||
|
||||
func Example_yamlDeclaredIdentityOnly() {
|
||||
result, err := configkit.Load(configkit.Bytes("YAML identity only", []byte(`
|
||||
configkit:
|
||||
sequences:
|
||||
- destination: [prompts]
|
||||
key: id
|
||||
prompts:
|
||||
- id: initial
|
||||
`)), configkit.Options[map[string]any]{
|
||||
Overrides: []configkit.Override{{
|
||||
Path: configkit.Keys("prompts"),
|
||||
Value: []any{
|
||||
map[string]any{"id": "same"},
|
||||
map[string]any{"id": "same"},
|
||||
},
|
||||
Flag: "--prompts",
|
||||
}},
|
||||
})
|
||||
fmt.Println(len(result.Config["prompts"].([]any)), err)
|
||||
// Output: 2 <nil>
|
||||
}
|
||||
|
||||
func TestYAMLDeclaredIdentityDoesNotRegisterFinalTypedConstraint(t *testing.T) {
|
||||
result, err := configkit.Load(configkit.Bytes("YAML identity only", []byte(`
|
||||
configkit:
|
||||
sequences:
|
||||
- destination: [prompts]
|
||||
key: id
|
||||
prompts:
|
||||
- id: initial
|
||||
`)), configkit.Options[map[string]any]{
|
||||
Overrides: []configkit.Override{{
|
||||
Path: configkit.Keys("prompts"),
|
||||
Value: []any{
|
||||
map[string]any{"id": "same"},
|
||||
map[string]any{"id": "same"},
|
||||
},
|
||||
Flag: "--prompts",
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("YAML-only identity incorrectly became an application constraint: %v", err)
|
||||
}
|
||||
prompts := result.Config["prompts"].([]any)
|
||||
if len(prompts) != 2 || prompts[0].(map[string]any)["id"] != prompts[1].(map[string]any)["id"] {
|
||||
t.Fatalf("CLI result = %#v", result.Config)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespacedTypedMapRejectsDuplicatesWithinOneCollection(t *testing.T) {
|
||||
_, err := consumers.LoadNamespacedMapCatalog(configkit.Bytes("duplicate namespace", []byte(`
|
||||
collections:
|
||||
team:
|
||||
prompts:
|
||||
- id: duplicate
|
||||
- id: duplicate
|
||||
`)))
|
||||
var loadError *configkit.LoadError
|
||||
if !errors.As(err, &loadError) || len(loadError.Diagnostics()) != 1 ||
|
||||
loadError.Diagnostics()[0].Code != configkit.CodeComposeConflict ||
|
||||
loadError.Diagnostics()[0].Path.String() != "/collections/team/prompts" {
|
||||
diagnostics := []configkit.Diagnostic(nil)
|
||||
if loadError != nil {
|
||||
diagnostics = loadError.Diagnostics()
|
||||
}
|
||||
t.Fatalf("duplicate namespaced identity error = %v (%#v)", err, diagnostics)
|
||||
}
|
||||
}
|
||||
7
examples/consumers/testdata/sequence-catalog/config.yml
vendored
Normal file
7
examples/consumers/testdata/sequence-catalog/config.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
run_label: source-default
|
||||
configkit:
|
||||
imports:
|
||||
- dir: fragments
|
||||
pattern: '*.yml'
|
||||
destination: [prompts]
|
||||
placement: sequence-item
|
||||
1
examples/consumers/testdata/sequence-catalog/foundation.txt
vendored
Normal file
1
examples/consumers/testdata/sequence-catalog/foundation.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Foundation resource text.
|
||||
4
examples/consumers/testdata/sequence-catalog/fragments/01-welcome.yml
vendored
Normal file
4
examples/consumers/testdata/sequence-catalog/fragments/01-welcome.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
id: welcome
|
||||
name: Welcome
|
||||
messages: [Welcome message]
|
||||
credential: CONFIGKIT_SEQUENCE_CATALOG_OLD_SECRET
|
||||
3
examples/consumers/testdata/sequence-catalog/fragments/02-concise.yml
vendored
Normal file
3
examples/consumers/testdata/sequence-catalog/fragments/02-concise.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
id: concise
|
||||
extends: {id: foundation}
|
||||
name: Concise
|
||||
10
examples/consumers/testdata/sequence-catalog/overlays/append.yml
vendored
Normal file
10
examples/consumers/testdata/sequence-catalog/overlays/append.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
configkit:
|
||||
sequences:
|
||||
- destination: [prompts]
|
||||
key: id
|
||||
mode: append
|
||||
prompts:
|
||||
- id: extra
|
||||
name: Extra
|
||||
messages: [Extra message]
|
||||
credential: CONFIGKIT_SEQUENCE_CATALOG_OLD_SECRET
|
||||
14
examples/consumers/testdata/sequence-catalog/overlays/replace.yml
vendored
Normal file
14
examples/consumers/testdata/sequence-catalog/overlays/replace.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
configkit:
|
||||
sequences:
|
||||
- destination: [prompts]
|
||||
key: id
|
||||
mode: replace
|
||||
prompts:
|
||||
- id: replacement
|
||||
name: Replacement
|
||||
messages: [Replacement message]
|
||||
credential: CONFIGKIT_SEQUENCE_CATALOG_OLD_SECRET
|
||||
- id: last
|
||||
name: Last
|
||||
messages: [Last message]
|
||||
credential: CONFIGKIT_SEQUENCE_CATALOG_OLD_SECRET
|
||||
14
examples/consumers/testdata/sequence-catalog/overlays/upsert.yml
vendored
Normal file
14
examples/consumers/testdata/sequence-catalog/overlays/upsert.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
configkit:
|
||||
sequences:
|
||||
- destination: [prompts]
|
||||
key: id
|
||||
mode: upsert
|
||||
prompts:
|
||||
- id: shared
|
||||
name: Shared replacement
|
||||
messages: [Replacement message]
|
||||
credential: CONFIGKIT_SEQUENCE_CATALOG_NEW_SECRET
|
||||
- id: extra
|
||||
name: Extra
|
||||
messages: [Extra message]
|
||||
credential: CONFIGKIT_SEQUENCE_CATALOG_OLD_SECRET
|
||||
18
examples/consumers/testdata/sequence-catalog/underlay.yml
vendored
Normal file
18
examples/consumers/testdata/sequence-catalog/underlay.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
configkit:
|
||||
sequences:
|
||||
- destination: [prompts]
|
||||
key: id
|
||||
mode: append
|
||||
prompts:
|
||||
- id: foundation
|
||||
name: Foundation
|
||||
description: Shared foundation instruction
|
||||
messages: [Foundation message]
|
||||
resource: foundation.txt
|
||||
credential: CONFIGKIT_SEQUENCE_CATALOG_OLD_SECRET
|
||||
- id: shared
|
||||
name: Shared draft
|
||||
description: This field must not survive a whole-record replacement
|
||||
messages: [Draft message]
|
||||
resource: shared.txt
|
||||
credential: CONFIGKIT_SEQUENCE_CATALOG_OLD_SECRET
|
||||
Reference in New Issue
Block a user