Migrate prompt fixtures to direct definitions

This commit is contained in:
2026-10-17 18:39:38 +00:00
parent 66b2994e48
commit c1fa18629d
9 changed files with 307 additions and 316 deletions

View File

@@ -1,6 +1,6 @@
# Dependency upgrades, discovery, and smoke testing implementation plan
Status: Stages 1–4 complete; Stage 5 is next.
Status: Stages 1–5 complete; Stage 6 is next.
## Authority and baseline
@@ -343,6 +343,16 @@ presentation, and error identity, not upstream detection algorithms.
**Complete when:** the previous delivered feature set works through v0.16.0
without compatibility scaffolding or implicit catalogs.
**Completed:** 2026-10-17. PromptKit fixtures now use direct prompt definitions,
explicit profiles/backends, and distinct IDs for multiple prompts. ckprompt
tests use the ID-only inspection and validation requests and verify that the
removed version flags are unavailable. Semantic YAML tests cover absent,
present-empty, and nonempty versions in prompt inspection, requirements, and
catalog reports; PromptKit-specific requirement classification assertions were
removed from the affected adapter/consumer tests. The ckprompt suite passed,
and composed named inspection, requirements show, and request validation all
ran successfully against the migrated fixture.
## Stage 6: Extend catalog contracts and engine adapters
**Primary files:** ckprompt public request declarations, `Suite`, `FromEngine`,

View File

@@ -19,31 +19,32 @@ import (
const adapterPrompt = `prompts:
briefing:
"1":
default_profile: default
inputs:
- name: topic
required: true
content_type: text/plain
description: "Topic"
- name: audience
required: false
content_type: text/plain
description: ""
messages:
- role: user
content: "Hello"
output:
format: text
validation_mode: none
version: "1"
default_profile: default
inputs:
- name: topic
required: true
content_type: text/plain
description: "Topic"
- name: audience
required: false
content_type: text/plain
description: ""
messages:
- role: user
content: "Hello"
output:
format: text
validation_mode: none
empty:
"1":
messages:
- role: user
content: "Empty inputs"
output:
format: text
validation_mode: none
version: "1"
default_profile: default
messages:
- role: user
content: "Empty inputs"
output:
format: text
validation_mode: none
backends:
local:
endpoint: "https://example.invalid/v1"
@@ -68,15 +69,15 @@ profiles:
const adapterRequirementsPrompt = `prompts:
briefing:
"1":
default_profile: default
session_id: '{{input "session-only"}} {{.session_variable}}'
messages:
- role: user
content: '{{if eq .audience "internal"}}{{input "internal-notes"}}{{else}}{{input "public-notes"}}{{end}} {{.audience}}'
output:
format: text
validation_mode: none
version: "1"
default_profile: default
session_id: '{{input "session-only"}} {{.session_variable}}'
messages:
- role: user
content: '{{if eq .audience "internal"}}{{input "internal-notes"}}{{else}}{{input "public-notes"}}{{end}} {{.audience}}'
output:
format: text
validation_mode: none
backends:
local:
endpoint: "https://example.invalid/v1"
@@ -319,22 +320,18 @@ func TestFromEnginePassesRequestsAndRetainsNilLoaderIssue(t *testing.T) {
return engine, nil
})
selection := ckconfig.Selection{Overlay: commandkit.Supplied[string]{Value: "overlay.yml", Set: true}}
version := commandkit.Supplied[string]{Value: "1", Set: true}
prompt, err := suite.InspectPrompt(wantContext, PromptRequest{Selection: selection, ID: "briefing", Version: version}, wantInput, wantState)
if err != nil || prompt.Value == nil || prompt.Value.PromptID != "briefing" || prompt.Value.PromptVersion != "1" || loaderCalls != 1 {
prompt, err := suite.InspectPrompt(wantContext, PromptRequest{Selection: selection, ID: "briefing"}, wantInput, wantState)
if err != nil || prompt.Value == nil || prompt.Value.PromptID != "briefing" || prompt.Value.PromptVersion == nil || *prompt.Value.PromptVersion != "1" || loaderCalls != 1 {
t.Fatalf("prompt inspection = (%#v, %v), calls=%d", prompt, err, loaderCalls)
}
defaultPrompt, err := suite.InspectPrompt(wantContext, PromptRequest{Selection: selection, ID: "briefing"}, wantInput, wantState)
if err != nil || defaultPrompt.Value == nil || defaultPrompt.Value.PromptVersion != "1" || loaderCalls != 2 {
t.Fatalf("empty version default = (%#v, %v), calls=%d", defaultPrompt, err, loaderCalls)
secondPrompt, err := suite.InspectPrompt(wantContext, PromptRequest{Selection: selection, ID: "briefing"}, wantInput, wantState)
if err != nil || secondPrompt.Value == nil || secondPrompt.Value.PromptID != "briefing" || loaderCalls != 2 {
t.Fatalf("ID-only prompt inspection = (%#v, %v), calls=%d", secondPrompt, err, loaderCalls)
}
profile, err := suite.InspectProfile(wantContext, ProfileRequest{Selection: selection, ID: "default"}, wantInput, wantState)
if err != nil || profile.Value == nil || profile.Value.ProfileID != "default" || loaderCalls != 3 {
t.Fatalf("profile inspection = (%#v, %v), calls=%d", profile, err, loaderCalls)
}
if version.Set != true || version.Value != "1" {
t.Fatalf("custom callback request lost version presence: %#v", version)
}
empty := FromEngine[int, int](nil)
empty.ListPrompts = nil
@@ -363,11 +360,10 @@ func TestFromEngineInspectsRequirementsWithNativeRequest(t *testing.T) {
request := RequirementsRequest{
Selection: selection,
Inspection: promptkit.RequirementInspectionRequest{
PromptID: "briefing",
PromptVersion: "1",
ProfileID: "default",
Vars: map[string]string{"audience": "internal"},
SessionID: "session-42",
PromptID: "briefing",
ProfileID: "default",
Vars: map[string]string{"audience": "internal"},
SessionID: "session-42",
},
}
want, err := engine.InspectRequirements(wantContext, request.Inspection)
@@ -387,26 +383,9 @@ func TestFromEngineInspectsRequirementsWithNativeRequest(t *testing.T) {
if err != nil || acquired.Value == nil || !reflect.DeepEqual(acquired.Value, want) || loaderCalls != 1 {
t.Fatalf("requirements inspection = (%#v, %v), calls=%d; want native result %#v", acquired, err, loaderCalls, want)
}
if acquired.Value.PromptID != "briefing" || acquired.Value.PromptVersion != "1" || acquired.Value.ProfileID != "default" {
if acquired.Value.PromptID != "briefing" || acquired.Value.PromptVersion == nil || *acquired.Value.PromptVersion != "1" || acquired.Value.ProfileID != "default" {
t.Fatalf("requirements selection = %#v", acquired.Value)
}
inputs := make(map[string]promptkit.InputRequirement, len(acquired.Value.Inputs))
for _, input := range acquired.Value.Inputs {
inputs[input.Name] = input
}
if inputs["internal-notes"].Kind != promptkit.RequirementRequired || inputs["public-notes"].Kind != promptkit.RequirementOptional {
t.Fatalf("caller variable did not reach native discovery: %#v", acquired.Value.Inputs)
}
if _, found := inputs["session-only"]; found {
t.Fatalf("session template was not bypassed by the supplied session ID: %#v", acquired.Value.Inputs)
}
variables := make(map[string]promptkit.VariableRequirement, len(acquired.Value.Variables))
for _, variable := range acquired.Value.Variables {
variables[variable.Name] = variable
}
if !variables["audience"].Supplied {
t.Fatalf("caller variable presence was not forwarded: %#v", acquired.Value.Variables)
}
}
func TestFromEngineRequirementsRetainLoadDiagnosticsAndSelectionErrors(t *testing.T) {

View File

@@ -6,6 +6,7 @@ import (
"errors"
"io"
"reflect"
"strconv"
"strings"
"testing"
@@ -24,6 +25,8 @@ func newPromptTestApp(t *testing.T, commands ...commandkit.Command) *commandkit.
return app
}
func testStringPointer(value string) *string { return &value }
func TestCommandBuildsIndependentOptionalTrees(t *testing.T) {
promptOnly := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{InspectPrompt: func(context.Context, PromptRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.PromptInspection], error) {
return commandkit.Acquired[*promptkit.PromptInspection]{}, errors.New("unused")
@@ -121,8 +124,7 @@ func TestRequirementsCommandCapturesContextAndVariableAssignments(t *testing.T)
providerCalls++
received, receivedInput, receivedState = request, value, gotState
return commandkit.Acquired[*promptkit.RequirementInspection]{Value: &promptkit.RequirementInspection{
PromptID: request.Inspection.PromptID, PromptVersion: request.Inspection.PromptVersion,
ProfileID: request.Inspection.ProfileID, Complete: true,
PromptID: request.Inspection.PromptID, ProfileID: request.Inspection.ProfileID, Complete: true,
}}, nil
},
}
@@ -139,7 +141,7 @@ func TestRequirementsCommandCapturesContextAndVariableAssignments(t *testing.T)
if familyOutcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(familyHelp.String(), "requirements") || strings.Contains(familyHelp.String(), "\n show ") || strings.Contains(familyHelp.String(), "\n list ") {
t.Fatalf("requirements-only family help = (%#v, %q)", familyOutcome, familyHelp.String())
}
for _, flag := range []string{"--config", "--overlay", "--version", "--profile", "--session-id", "--var", "--format", "--campaign"} {
for _, flag := range []string{"--config", "--overlay", "--profile", "--session-id", "--var", "--format", "--campaign"} {
if !strings.Contains(help.String(), flag) {
t.Errorf("requirements help omits %s: %q", flag, help.String())
}
@@ -152,7 +154,7 @@ func TestRequirementsCommandCapturesContextAndVariableAssignments(t *testing.T)
args := []string{
"promptkit", "requirements", "show", "briefing", "--campaign", "spring",
"--config", "before.yml", "--config=", "--overlay", "base.yml", "--overlay=",
"--version", "1", "--version", "2", "--profile", "first", "--profile", "editorial",
"--profile", "first", "--profile", "editorial",
"--session-id", "old-session", "--session-id", "session-42", "--format", "text", "--format", "yaml",
"--var", "note=hello, world", "--var", "empty=", "--var", "token=a=b", "--var", "=value",
"--var", "mode=brief", "--var", "mode=full",
@@ -168,7 +170,7 @@ func TestRequirementsCommandCapturesContextAndVariableAssignments(t *testing.T)
t.Fatalf("configuration selection = %#v", received.Selection)
}
wantVariables := map[string]string{"note": "hello, world", "empty": "", "token": "a=b", "": "value", "mode": "full"}
if received.Inspection.PromptID != "briefing" || received.Inspection.PromptVersion != "2" || received.Inspection.ProfileID != "editorial" || received.Inspection.SessionID != "session-42" || !reflect.DeepEqual(received.Inspection.Vars, wantVariables) {
if received.Inspection.PromptID != "briefing" || received.Inspection.ProfileID != "editorial" || received.Inspection.SessionID != "session-42" || !reflect.DeepEqual(received.Inspection.Vars, wantVariables) {
t.Fatalf("native request = %#v, want vars %#v", received.Inspection, wantVariables)
}
if !receivedInput.campaign.Set || receivedInput.campaign.Value != "spring" || receivedState.call != 1 {
@@ -184,7 +186,7 @@ func TestRequirementsCommandCapturesContextAndVariableAssignments(t *testing.T)
if outcome.Kind != commandkit.OutcomeSuccess || newInputCalls != 2 || checkInputCalls != 2 || newStateCalls != 2 || providerCalls != 2 {
t.Fatalf("second invocation = %#v, calls=%d/%d/%d/%d", outcome, newInputCalls, checkInputCalls, newStateCalls, providerCalls)
}
if len(received.Inspection.Vars) != 0 || received.Inspection.PromptVersion != "" || received.Inspection.ProfileID != "" || received.Inspection.SessionID != "" {
if len(received.Inspection.Vars) != 0 || received.Inspection.ProfileID != "" || received.Inspection.SessionID != "" {
t.Fatalf("second invocation retained native values: %#v", received.Inspection)
}
var defaultReport map[string]any
@@ -299,7 +301,7 @@ func TestCatalogListUsesPreparedInputAndPreflightsFormat(t *testing.T) {
ListPrompts: func(_ context.Context, selection ckconfig.Selection, value input, gotState state) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
providerCalls++
received, receivedSelection, receivedState = value, selection, gotState
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: "1"}}}, nil
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: testStringPointer("1")}}}, nil
},
}
app := newPromptTestApp(t, Command(suite))
@@ -437,9 +439,10 @@ func TestCatalogNilFactoriesSupplyZeroValues(t *testing.T) {
func TestCatalogCommandsRenderNativeValuesAndEmptySequences(t *testing.T) {
prompts := []promptkit.PromptIdentity{
{ID: "later", Version: "1.10"},
{ID: "001", Version: " 03 "},
{ID: "a: true", Version: "yes"},
{ID: "later", Version: testStringPointer("1.10")},
{ID: "001", Version: testStringPointer(" 03 ")},
{ID: "a: true", Version: testStringPointer("yes")},
{ID: "without-version"},
}
profiles := []string{"later", "001", "a: true", "later"}
suite := Suite[struct{}, struct{}]{
@@ -465,9 +468,16 @@ func TestCatalogCommandsRenderNativeValuesAndEmptySequences(t *testing.T) {
}
for index, entry := range promptEntries {
id, idOK := entry["id"].(string)
version, versionOK := entry["version"].(string)
if len(entry) != 2 || !idOK || !versionOK || id != prompts[index].ID || version != prompts[index].Version {
t.Errorf("prompt entry %d = %#v, want string id/version %q/%q", index, entry, prompts[index].ID, prompts[index].Version)
if !idOK || id != prompts[index].ID {
t.Errorf("prompt entry %d = %#v, want ID %q", index, entry, prompts[index].ID)
}
version, versionExists := entry["version"]
if prompts[index].Version == nil {
if versionExists || len(entry) != 1 {
t.Errorf("prompt entry %d emitted absent version: %#v", index, entry)
}
} else if !versionExists || version != *prompts[index].Version || len(entry) != 2 {
t.Errorf("prompt entry %d version = %#v, want %q", index, version, *prompts[index].Version)
}
}
@@ -565,7 +575,7 @@ func TestCatalogFailuresRetainDiagnosticsAndDeliveryState(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
canceled := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{ListPrompts: func(context.Context, ckconfig.Selection, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
cancel()
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: "1"}}, Diagnostics: []commandkit.Diagnostic{diagnostic}}, nil
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: testStringPointer("1")}}, Diagnostics: []commandkit.Diagnostic{diagnostic}}, nil
}}))
var output bytes.Buffer
outcome = canceled.Execute(ctx, []string{"promptkit", "list", "prompts"}, commandkit.Streams{Out: &output, Err: io.Discard})
@@ -584,7 +594,7 @@ func TestCatalogFailuresRetainDiagnosticsAndDeliveryState(t *testing.T) {
} {
t.Run(test.name, func(t *testing.T) {
writerApp := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{ListPrompts: func(context.Context, ckconfig.Selection, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: "1"}}, Diagnostics: []commandkit.Diagnostic{diagnostic}}, nil
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: testStringPointer("1")}}, Diagnostics: []commandkit.Diagnostic{diagnostic}}, nil
}}))
outcome := writerApp.Execute(context.Background(), []string{"promptkit", "list", "prompts"}, commandkit.Streams{Out: test.writer, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.DeliveryError, test.want) || !errors.Is(outcome.Cause, test.want) || !outcome.ReportIncomplete || !hasCatalogWarning(outcome.Diagnostics) {
@@ -663,22 +673,22 @@ func TestDefinitionInputAndStateLifetimesFollowChecks(t *testing.T) {
},
InspectPrompt: func(ctx context.Context, request PromptRequest, input *inspectInput, state *runState) (commandkit.Acquired[*promptkit.PromptInspection], error) {
callbacks++
if ctx != wantContext || input != checked || request.ID != "briefing" || !request.Version.Set || request.Version.Value != "2" || state == nil {
if ctx != wantContext || input != checked || request.ID != "briefing" || state == nil {
t.Errorf("prompt callback arguments = ctx=%v request=%#v input=%p state=%p", ctx, request, input, state)
}
return commandkit.Acquired[*promptkit.PromptInspection]{Value: &promptkit.PromptInspection{PromptID: request.ID, PromptVersion: request.Version.Value}}, nil
return commandkit.Acquired[*promptkit.PromptInspection]{Value: &promptkit.PromptInspection{PromptID: request.ID}}, nil
},
}
app := newPromptTestApp(t, Command(suite))
wantContext = context.WithValue(context.Background(), struct{}{}, "context")
var output bytes.Buffer
outcome := app.Execute(wantContext, []string{"promptkit", "show", "prompt", "briefing", "--campaign", "spring", "--config", "base.yml", "--overlay", "local.yml", "--version", "2"}, commandkit.Streams{Out: &output, Err: io.Discard})
outcome := app.Execute(wantContext, []string{"promptkit", "show", "prompt", "briefing", "--campaign", "spring", "--config", "base.yml", "--overlay", "local.yml"}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || newInputs != 1 || newStates != 1 || checks != 1 || callbacks != 1 || conversions != 1 {
t.Fatalf("execution outcome=%#v counts conversion/input/state/check/callback=%d/%d/%d/%d/%d report=%q", outcome, conversions, newInputs, newStates, checks, callbacks, output.String())
}
rejectInput = true
output.Reset()
outcome = app.Execute(wantContext, []string{"promptkit", "show", "prompt", "briefing", "--campaign", "spring", "--config", "base.yml", "--overlay", "local.yml", "--version", "2"}, commandkit.Streams{Out: &output, Err: io.Discard})
outcome = app.Execute(wantContext, []string{"promptkit", "show", "prompt", "briefing", "--campaign", "spring", "--config", "base.yml", "--overlay", "local.yml"}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeUsage || newInputs != 2 || newStates != 1 || checks != 2 || callbacks != 1 || conversions != 2 {
t.Fatalf("rejected input reached state/callback: outcome=%#v counts=%d/%d/%d/%d/%d", outcome, conversions, newInputs, newStates, checks, callbacks)
}
@@ -694,7 +704,7 @@ func TestDefinitionReportsRetainNativeFieldsAndSafeCredentialReferences(t *testi
})
app := newPromptTestApp(t, Command(suite))
var output bytes.Buffer
outcome := app.Execute(context.Background(), []string{"promptkit", "show", "prompt", "briefing", "--version", "1", "--format", "yaml"}, commandkit.Streams{Out: &output, Err: io.Discard})
outcome := app.Execute(context.Background(), []string{"promptkit", "show", "prompt", "briefing", "--format", "yaml"}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || loadCalls != 1 {
t.Fatalf("prompt report outcome=%#v load calls=%d report=%s", outcome, loadCalls, output.String())
}
@@ -756,6 +766,75 @@ func TestDefinitionReportsRetainNativeFieldsAndSafeCredentialReferences(t *testi
}
}
func TestOptionalPromptVersionsPreserveAbsenceAndEmptyValues(t *testing.T) {
empty := ""
label := "release-1"
for _, test := range []struct {
name string
version *string
present bool
want string
}{
{name: "absent"},
{name: "present empty", version: &empty, present: true, want: ""},
{name: "nonempty", version: &label, present: true, want: "release-1"},
} {
t.Run(test.name, func(t *testing.T) {
prompt := promptReportFromInspection(&promptkit.PromptInspection{PromptID: "briefing", PromptVersion: test.version})
promptBytes, err := encodePromptYAMLBytes(prompt)
if err != nil {
t.Fatal(err)
}
var promptReport map[string]any
if err := yaml.Unmarshal(promptBytes, &promptReport); err != nil {
t.Fatal(err)
}
assertOptionalVersion(t, promptReport, test.present, test.want)
text := formatPromptText(prompt)
if test.present && !strings.Contains(text, "Prompt version: "+strconv.Quote(test.want)+"\n") {
t.Errorf("text omitted present version %q: %q", test.want, text)
}
if !test.present && strings.Contains(text, "Prompt version:") {
t.Errorf("text emitted absent version: %q", text)
}
requirementsBytes, err := encodePromptYAMLBytes(requirementsReportFromInspection(&promptkit.RequirementInspection{PromptID: "briefing", PromptVersion: test.version}))
if err != nil {
t.Fatal(err)
}
var requirementsReport map[string]any
if err := yaml.Unmarshal(requirementsBytes, &requirementsReport); err != nil {
t.Fatal(err)
}
assertOptionalVersion(t, requirementsReport, test.present, test.want)
catalogBytes, err := encodePromptCatalog([]promptkit.PromptIdentity{{ID: "briefing", Version: test.version}})
if err != nil {
t.Fatal(err)
}
var catalog []map[string]any
if err := yaml.Unmarshal(catalogBytes, &catalog); err != nil {
t.Fatal(err)
}
if len(catalog) != 1 || catalog[0]["id"] != "briefing" {
t.Fatalf("catalog = %#v", catalog)
}
assertOptionalVersion(t, catalog[0], test.present, test.want)
})
}
}
func assertOptionalVersion(t *testing.T, report map[string]any, present bool, want string) {
t.Helper()
version, found := report["version"]
if found != present {
t.Fatalf("version presence = %t in %#v, want %t", found, report, present)
}
if present && version != want {
t.Errorf("version = %#v, want %q", version, want)
}
}
func TestNativeLookupErrorsAndBlankIDsReachCallbacks(t *testing.T) {
nativeErr := errors.New("native lookup failure")
calls := 0
@@ -780,6 +859,18 @@ func TestNativeLookupErrorsAndBlankIDsReachCallbacks(t *testing.T) {
}
}
func TestNamedPromptInspectionRejectsRemovedVersionFlag(t *testing.T) {
calls := 0
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{InspectPrompt: func(_ context.Context, request PromptRequest, _, _ struct{}) (commandkit.Acquired[*promptkit.PromptInspection], error) {
calls++
return commandkit.Acquired[*promptkit.PromptInspection]{Value: &promptkit.PromptInspection{PromptID: request.ID}}, nil
}}))
outcome := app.Execute(context.Background(), []string{"promptkit", "show", "prompt", "briefing", "--version", "1"}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeUsage || calls != 0 {
t.Fatalf("removed version flag outcome = %#v, provider calls=%d", outcome, calls)
}
}
func TestCancelledInspectionDoesNotRender(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
called := 0
@@ -838,9 +929,9 @@ func TestProfileTextUsesCredentialNamesAndExplicitEmptyExtraParams(t *testing.T)
func TestPromptCatalogYAMLPreservesSchemaStringsAndOrder(t *testing.T) {
want := []promptkit.PromptIdentity{
{ID: " briefing ", Version: " 03 "},
{ID: "001", Version: "1.10"},
{ID: " briefing ", Version: " 03 "},
{ID: " briefing ", Version: testStringPointer(" 03 ")},
{ID: "001", Version: testStringPointer("1.10")},
{ID: " briefing ", Version: testStringPointer(" 03 ")},
}
output, err := encodePromptCatalog(want)
if err != nil {
@@ -859,8 +950,8 @@ func TestPromptCatalogYAMLPreservesSchemaStringsAndOrder(t *testing.T) {
}
id, idOK := entry["id"].(string)
version, versionOK := entry["version"].(string)
if !idOK || !versionOK || id != want[index].ID || version != want[index].Version {
t.Errorf("entry %d = %#v, want id/version %q/%q as strings", index, entry, want[index].ID, want[index].Version)
if !idOK || !versionOK || id != want[index].ID || want[index].Version == nil || version != *want[index].Version {
t.Errorf("entry %d = %#v, want id/version %q/%#v", index, entry, want[index].ID, want[index].Version)
}
}
}
@@ -917,11 +1008,14 @@ func TestRequirementsReportRendersEmptyInventoriesAndFalseValues(t *testing.T) {
if err := yaml.Unmarshal(output, &report); err != nil {
t.Fatalf("decode requirements report %q: %v", output, err)
}
for _, key := range []string{"prompt_id", "prompt_version", "profile_id", "complete", "inputs", "variables", "issues"} {
for _, key := range []string{"prompt_id", "profile_id", "complete", "inputs", "variables", "issues"} {
if _, ok := report[key]; !ok {
t.Errorf("requirements report is missing %q: %q", key, output)
}
}
if _, ok := report["version"]; ok {
t.Errorf("absent prompt version was emitted: %q", output)
}
if report["complete"] != false {
t.Errorf("complete = %#v, want false", report["complete"])
}
@@ -931,7 +1025,7 @@ func TestRequirementsReportRendersEmptyInventoriesAndFalseValues(t *testing.T) {
t.Errorf("%s = %#v, want an empty sequence", key, report[key])
}
}
for _, key := range []string{"prompt_id", "prompt_version", "profile_id"} {
for _, key := range []string{"prompt_id", "profile_id"} {
if report[key] != "" {
t.Errorf("%s = %#v, want an explicitly present empty string", key, report[key])
}
@@ -943,7 +1037,7 @@ func TestRequirementsReportPreservesNativeFieldsAndMetadata(t *testing.T) {
contentType := "true"
inspection := &promptkit.RequirementInspection{
PromptID: "true",
PromptVersion: "001",
PromptVersion: testStringPointer("001"),
ProfileID: "null",
Complete: true,
Inputs: []promptkit.InputRequirement{
@@ -973,7 +1067,7 @@ func TestRequirementsReportPreservesNativeFieldsAndMetadata(t *testing.T) {
if err := yaml.Unmarshal(output, &report); err != nil {
t.Fatalf("decode requirements report %q: %v", output, err)
}
if report["complete"] != complete || report["prompt_id"] != "true" || report["prompt_version"] != "001" || report["profile_id"] != "null" {
if report["complete"] != complete || report["prompt_id"] != "true" || report["version"] != "001" || report["profile_id"] != "null" {
t.Fatalf("selected context or completeness = %#v", report)
}

View File

@@ -225,13 +225,14 @@ func comparisonEngine(t *testing.T, greeting string) *promptkit.Engine {
t.Helper()
configuration := `prompts:
briefing:
"1":
messages:
- role: user
content: "Hello"
output:
format: text
validation_mode: none
version: "1"
default_profile: default
messages:
- role: user
content: "Hello"
output:
format: text
validation_mode: none
backends:
local:
endpoint: "https://example.invalid/v1"

View File

@@ -70,13 +70,14 @@ func TestPublicConfigurationAndDefinitionFamiliesRemainIndependent(t *testing.T)
inputs, err := promptkit.FilesystemInputs("composition fixture", fstest.MapFS{
"config/prompts.yml": &fstest.MapFile{Data: []byte(`prompts:
briefing:
"1":
messages:
- role: user
content: "Hello"
output:
format: text
validation_mode: none
version: "1"
default_profile: default
messages:
- role: user
content: "Hello"
output:
format: text
validation_mode: none
backends:
local:
endpoint: "https://example.invalid/v1"

View File

@@ -118,12 +118,11 @@ func loadPromptEngine(ctx context.Context, selection ckconfig.Selection, input *
},
}},
"prompts": map[string]any{chosen.PromptID: map[string]any{
chosen.PromptVersion: map[string]any{
"default_profile": chosen.ProfileID,
"inputs": []any{map[string]any{"name": "topic", "required": true, "content_type": "text/plain", "description": "Topic to brief"}},
"messages": []any{map[string]any{"role": "user", "content": "Write a concise briefing about {{.topic}}. {{if .mode}}{{input \"notes\"}}{{end}}"}},
"output": map[string]any{"format": "text", "validation_mode": "none"},
},
"version": chosen.PromptVersion,
"default_profile": chosen.ProfileID,
"inputs": []any{map[string]any{"name": "topic", "required": true, "content_type": "text/plain", "description": "Topic to brief"}},
"messages": []any{map[string]any{"role": "user", "content": "Write a concise briefing about {{.topic}}. {{if .mode}}{{input \"notes\"}}{{end}}"}},
"output": map[string]any{"format": "text", "validation_mode": "none"},
}},
})
return promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{Source: engineConfig}}})

View File

@@ -127,21 +127,11 @@ func TestComposedRequirementsDiscoveryUsesSelectedCampaign(t *testing.T) {
}
for _, test := range []struct {
name string
args []string
wantMode bool
wantModeIssue bool
name string
args []string
}{
{
name: "conditional dependency is unresolved without mode",
args: []string{"promptkit", "requirements", "show", "briefing", "--campaign", "spring"},
wantModeIssue: true,
},
{
name: "supplied mode discovers conditional notes",
args: []string{"promptkit", "requirements", "show", "briefing", "--campaign", "spring", "--var", "mode=full", "--format", "yaml"},
wantMode: true,
},
{name: "without variables", args: []string{"promptkit", "requirements", "show", "briefing", "--campaign", "spring"}},
{name: "with caller variable", args: []string{"promptkit", "requirements", "show", "briefing", "--campaign", "spring", "--var", "mode=full", "--format", "yaml"}},
} {
t.Run(test.name, func(t *testing.T) {
var output bytes.Buffer
@@ -150,65 +140,17 @@ func TestComposedRequirementsDiscoveryUsesSelectedCampaign(t *testing.T) {
t.Fatalf("requirements outcome = %#v, report %q", outcome, output.String())
}
var report struct {
PromptID string `yaml:"prompt_id"`
PromptVersion string `yaml:"prompt_version"`
ProfileID string `yaml:"profile_id"`
Complete bool `yaml:"complete"`
Inputs []struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Declared bool `yaml:"declared"`
} `yaml:"inputs"`
Variables []struct {
Name string `yaml:"name"`
Supplied bool `yaml:"supplied"`
} `yaml:"variables"`
Issues []struct {
Code string `yaml:"code"`
InputName string `yaml:"input_name"`
VariableName string `yaml:"variable_name"`
} `yaml:"issues"`
}
var report map[string]any
if err := yaml.Unmarshal(output.Bytes(), &report); err != nil {
t.Fatalf("decode requirements report %q: %v", output.String(), err)
}
if report.PromptID != "briefing" || report.PromptVersion != "1" || report.ProfileID != "spring" {
t.Fatalf("requirements identity = %#v", report)
}
foundTopic, foundNotes, foundMode := false, false, false
for _, input := range report.Inputs {
switch input.Name {
case "topic":
foundTopic = input.Declared && input.Kind == "required"
case "notes":
foundNotes = true
wantKind := "conditional"
if test.wantMode {
wantKind = "required"
}
if input.Declared || input.Kind != wantKind {
t.Errorf("notes input classification = %#v; want undeclared %s", input, wantKind)
}
for _, key := range []string{"prompt_id", "version", "profile_id", "complete", "inputs", "variables", "issues"} {
if _, found := report[key]; !found {
t.Errorf("requirements report is missing %q: %#v", key, report)
}
}
for _, variable := range report.Variables {
if variable.Name == "mode" {
foundMode = variable.Supplied == test.wantMode
}
}
if !foundTopic || !foundNotes || !foundMode {
t.Fatalf("requirements inventory topic=%t notes=%t mode=%t; report %#v", foundTopic, foundNotes, foundMode, report)
}
if test.wantModeIssue && (len(report.Issues) != 1 || report.Issues[0].Code != "missing_variable" || report.Issues[0].VariableName != "mode") {
t.Fatalf("missing mode issue = %#v", report.Issues)
}
if !test.wantModeIssue && len(report.Issues) != 0 {
t.Fatalf("requirements issues with mode supplied=%t: %#v", test.wantMode, report.Issues)
}
if report.Complete != test.wantMode {
t.Fatalf("requirements completeness = %t with mode supplied=%t; report %#v", report.Complete, test.wantMode, report)
if report["prompt_id"] != "briefing" || report["version"] != "1" || report["profile_id"] != "spring" {
t.Fatalf("requirements report metadata = %#v", report)
}
})
}
@@ -268,19 +210,18 @@ func TestBuildValidationRequestDefaultsOnlyAbsentVariables(t *testing.T) {
inputs := map[string]promptkit.ArtifactRef{"topic": promptkit.File("fixtures/topic.txt")}
explicit := map[string]string{"topic": "", "mode": "", "audience": ""}
seed := ckprompt.ValidationRequest{Request: promptkit.RunRequest{
PromptID: "briefing",
PromptVersion: "2",
ProfileID: "custom-profile",
SessionID: "session-9",
Vars: explicit,
Inputs: inputs,
PromptID: "briefing",
ProfileID: "custom-profile",
SessionID: "session-9",
Vars: explicit,
Inputs: inputs,
}}
got, err := buildValidationRequest(context.Background(), seed, input, cache)
if err != nil {
t.Fatal(err)
}
if got.PromptID != seed.Request.PromptID || got.PromptVersion != seed.Request.PromptVersion || got.ProfileID != seed.Request.ProfileID || got.SessionID != seed.Request.SessionID {
if got.PromptID != seed.Request.PromptID || got.ProfileID != seed.Request.ProfileID || got.SessionID != seed.Request.SessionID {
t.Fatalf("request identity/context changed: %#v", got)
}
if got.Vars["topic"] != "" || got.Vars["mode"] != "" || got.Vars["audience"] != "" {

View File

@@ -17,41 +17,40 @@ import (
const requirementsCommandPrompt = `prompts:
briefing:
"2":
default_profile: editorial
session_id: '{{input "session-only"}} {{.session_variable}}'
messages:
- role: user
content: '{{if .mode}}{{input "notes"}}{{end}}'
output:
format: text
validation_mode: none
version: "2"
default_profile: editorial
session_id: '{{input "session-only"}} {{.session_variable}}'
messages:
- role: user
content: '{{if .mode}}{{input "notes"}}{{end}}'
output:
format: text
validation_mode: none
required-variable:
"1":
default_profile: editorial
messages:
- role: user
content: '{{.required}}'
output:
format: text
validation_mode: none
ambiguous:
"1":
default_profile: editorial
messages:
- role: user
content: "First version"
output:
format: text
validation_mode: none
"2":
default_profile: editorial
messages:
- role: user
content: "Second version"
output:
format: text
validation_mode: none
version: "1"
default_profile: editorial
messages:
- role: user
content: '{{.required}}'
output:
format: text
validation_mode: none
ambiguous-first:
default_profile: editorial
messages:
- role: user
content: "First definition"
output:
format: text
validation_mode: none
ambiguous-second:
default_profile: editorial
messages:
- role: user
content: "Second definition"
output:
format: text
validation_mode: none
backends:
local:
endpoint: "https://example.invalid/v1"
@@ -101,7 +100,7 @@ func executeRequirementsAgainstNative(t *testing.T, engine *promptkit.Engine, re
return native, outcome, actual, diagnostic.String()
}
func TestRequirementsCommandMatchesNativePromptKitDiscovery(t *testing.T) {
func TestRequirementsCommandForwardsNativeRequestAndProjectsInspection(t *testing.T) {
engine := requirementsCommandEngine(t)
for _, test := range []struct {
name string
@@ -109,82 +108,50 @@ func TestRequirementsCommandMatchesNativePromptKitDiscovery(t *testing.T) {
args []string
}{
{
name: "partial conditional discovery",
request: promptkit.RequirementInspectionRequest{PromptID: "briefing", PromptVersion: "2", ProfileID: "editorial"},
args: []string{"promptkit", "requirements", "show", "briefing", "--version", "2", "--profile", "editorial"},
},
{
name: "supplied branch variable",
request: promptkit.RequirementInspectionRequest{
PromptID: "briefing", PromptVersion: "2", ProfileID: "editorial", Vars: map[string]string{"mode": "full"},
},
args: []string{"promptkit", "requirements", "show", "briefing", "--version", "2", "--profile", "editorial", "--var", "mode=full", "--format", "yaml"},
},
{
name: "complete with an unsupplied required variable",
request: promptkit.RequirementInspectionRequest{PromptID: "required-variable", PromptVersion: "1", ProfileID: "editorial"},
args: []string{"promptkit", "requirements", "show", "required-variable", "--version", "1", "--profile", "editorial"},
},
{
name: "native version and default profile selection",
request: promptkit.RequirementInspectionRequest{PromptID: "briefing"},
args: []string{"promptkit", "requirements", "show", "briefing"},
},
{
name: "session template discovery",
name: "conditional discovery",
request: promptkit.RequirementInspectionRequest{PromptID: "briefing", ProfileID: "editorial"},
args: []string{"promptkit", "requirements", "show", "briefing", "--profile", "editorial"},
},
{
name: "session ID bypasses the session template",
name: "supplied branch variable",
request: promptkit.RequirementInspectionRequest{PromptID: "briefing", ProfileID: "editorial", Vars: map[string]string{"mode": "full"}},
args: []string{"promptkit", "requirements", "show", "briefing", "--profile", "editorial", "--var", "mode=full", "--format", "yaml"},
},
{
name: "prompt with a required variable",
request: promptkit.RequirementInspectionRequest{PromptID: "required-variable", ProfileID: "editorial"},
args: []string{"promptkit", "requirements", "show", "required-variable", "--profile", "editorial"},
},
{
name: "default profile selection",
request: promptkit.RequirementInspectionRequest{PromptID: "briefing"},
args: []string{"promptkit", "requirements", "show", "briefing"},
},
{
name: "first distinct prompt ID",
request: promptkit.RequirementInspectionRequest{PromptID: "ambiguous-first", ProfileID: "editorial"},
args: []string{"promptkit", "requirements", "show", "ambiguous-first", "--profile", "editorial"},
},
{
name: "second distinct prompt ID",
request: promptkit.RequirementInspectionRequest{PromptID: "ambiguous-second", ProfileID: "editorial"},
args: []string{"promptkit", "requirements", "show", "ambiguous-second", "--profile", "editorial"},
},
{
name: "session template context",
request: promptkit.RequirementInspectionRequest{PromptID: "briefing", ProfileID: "editorial"},
args: []string{"promptkit", "requirements", "show", "briefing", "--profile", "editorial"},
},
{
name: "session ID context",
request: promptkit.RequirementInspectionRequest{PromptID: "briefing", ProfileID: "editorial", SessionID: "session-42"},
args: []string{"promptkit", "requirements", "show", "briefing", "--profile", "editorial", "--session-id", "session-42"},
},
} {
t.Run(test.name, func(t *testing.T) {
native, outcome, _, _ := executeRequirementsAgainstNative(t, engine, test.request, test.args)
_, outcome, _, _ := executeRequirementsAgainstNative(t, engine, test.request, test.args)
if outcome.Status != 0 {
t.Fatalf("partial or complete inspection returned status %d", outcome.Status)
}
switch test.name {
case "partial conditional discovery":
if native.Complete || len(native.Issues) == 0 || len(native.Inputs) == 0 {
t.Fatalf("partial discovery lost useful evidence: %#v", native)
}
case "supplied branch variable":
if !native.Complete || len(native.Issues) != 0 {
t.Fatalf("resolved discovery = %#v", native)
}
case "complete with an unsupplied required variable":
if !native.Complete || len(native.Variables) != 1 || native.Variables[0].Supplied {
t.Fatalf("complete discovery with unsupplied variable = %#v", native)
}
case "native version and default profile selection":
if native.PromptVersion != "2" || native.ProfileID != "editorial" {
t.Fatalf("native default selection = %#v", native)
}
case "session template discovery":
foundInput, foundVariable := false, false
for _, input := range native.Inputs {
foundInput = foundInput || input.Name == "session-only"
}
for _, variable := range native.Variables {
foundVariable = foundVariable || variable.Name == "session_variable"
}
if !foundInput || !foundVariable {
t.Fatalf("session template dependencies missing: %#v", native)
}
case "session ID bypasses the session template":
for _, input := range native.Inputs {
if input.Name == "session-only" {
t.Fatalf("session template input remained with session ID: %#v", native.Inputs)
}
}
for _, variable := range native.Variables {
if variable.Name == "session_variable" {
t.Fatalf("session template variable remained with session ID: %#v", native.Variables)
}
}
t.Fatalf("inspection returned status %d", outcome.Status)
}
})
}
@@ -193,40 +160,38 @@ func TestRequirementsCommandMatchesNativePromptKitDiscovery(t *testing.T) {
func TestRequirementsCommandPreservesNativeSelectionErrors(t *testing.T) {
engine := requirementsCommandEngine(t)
for _, test := range []struct {
name string
request promptkit.RequirementInspectionRequest
args []string
cause error
name string
args []string
cause error
}{
{
name: "missing prompt",
request: promptkit.RequirementInspectionRequest{PromptID: "missing", PromptVersion: "2", ProfileID: "editorial"},
args: []string{"promptkit", "requirements", "show", "missing", "--version", "2", "--profile", "editorial"},
cause: promptkit.ErrPromptNotFound,
name: "missing prompt",
args: []string{"promptkit", "requirements", "show", "missing", "--profile", "editorial"},
cause: promptkit.ErrPromptNotFound,
},
{
name: "missing profile",
request: promptkit.RequirementInspectionRequest{PromptID: "briefing", PromptVersion: "2", ProfileID: "missing"},
args: []string{"promptkit", "requirements", "show", "briefing", "--version", "2", "--profile", "missing"},
cause: promptkit.ErrProfileNotFound,
name: "missing profile",
args: []string{"promptkit", "requirements", "show", "briefing", "--profile", "missing"},
cause: promptkit.ErrProfileNotFound,
},
{
name: "ambiguous prompt version",
request: promptkit.RequirementInspectionRequest{PromptID: "ambiguous", ProfileID: "editorial"},
args: []string{"promptkit", "requirements", "show", "ambiguous", "--profile", "editorial"},
cause: promptkit.ErrPromptLoad,
name: "removed prompt version flag",
args: []string{"promptkit", "requirements", "show", "briefing", "--version", "2", "--profile", "editorial"},
cause: nil,
},
} {
t.Run(test.name, func(t *testing.T) {
_, nativeErr := engine.InspectRequirements(context.Background(), test.request)
if !errors.Is(nativeErr, test.cause) {
t.Fatalf("native selection error = %v, want %v", nativeErr, test.cause)
}
app := newPromptTestApp(t, Command(FromEngine[int, int](func(context.Context, ckconfig.Selection, int, int) (*promptkit.Engine, error) {
return engine, nil
})))
var output bytes.Buffer
outcome := app.Execute(context.Background(), test.args, commandkit.Streams{Out: &output, Err: io.Discard})
if test.cause == nil {
if outcome.Kind != commandkit.OutcomeUsage || output.Len() != 0 {
t.Fatalf("removed flag outcome = %#v output=%q", outcome, output.String())
}
return
}
if outcome.Kind != commandkit.OutcomeOperational || outcome.Status != 1 || !errors.Is(outcome.Cause, test.cause) || output.Len() != 0 {
t.Fatalf("native selection outcome = %#v output=%q", outcome, output.String())
}

View File

@@ -64,13 +64,13 @@ func TestValidationCommandComposesWithRequirementsShow(t *testing.T) {
outcome = app.Execute(context.Background(), []string{
"promptkit", "requirements", "validate", "briefing",
"--config", "config.yml", "--overlay", "overlay.yml", "--campaign", "spring",
"--version", "v2", "--profile", "editorial", "--session-id", "session-1",
"--profile", "editorial", "--session-id", "session-1",
"--var", "topic=roadmap", "--input", "notes=fixtures/notes=final.txt", "--format", "yaml",
}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || outcome.Status != 0 || validateCalls != 1 {
t.Fatalf("validation outcome = (%#v, %q), calls=%d", outcome, output.String(), validateCalls)
}
if received.Selection != wantSelection || received.Request.PromptID != "briefing" || received.Request.PromptVersion != "v2" || received.Request.ProfileID != "editorial" || received.Request.SessionID != "session-1" {
if received.Selection != wantSelection || received.Request.PromptID != "briefing" || received.Request.ProfileID != "editorial" || received.Request.SessionID != "session-1" {
t.Errorf("validation request = %#v", received)
}
if received.Request.Vars["topic"] != "roadmap" || len(received.Request.Vars) != 1 {
@@ -197,7 +197,7 @@ func TestValidationCommandPreservesPresenceAndRepeatedAssignmentsPerInvocation(t
firstArgs := []string{
"promptkit", "requirements", "validate", "briefing", "--config", "", "--overlay", "",
"--version", "", "--profile", "", "--session-id", "", "--campaign", "spring",
"--profile", "", "--session-id", "", "--campaign", "spring",
"--var", "=", "--var", "phrase= two words, three ", "--var", "duplicate=first", "--var", "duplicate=last=value",
"--input", "=", "--input", "file= path, with commas=and=equals ", "--input", "duplicate=first.txt", "--input", "duplicate=last=path.txt",
}
@@ -209,7 +209,7 @@ func TestValidationCommandPreservesPresenceAndRepeatedAssignmentsPerInvocation(t
if first.Selection.Config.Value != "" || !first.Selection.Config.Set || first.Selection.Overlay.Value != "" || !first.Selection.Overlay.Set {
t.Errorf("explicit empty selection = %#v", first.Selection)
}
if first.Request.PromptVersion != "" || first.Request.ProfileID != "" || first.Request.SessionID != "" {
if first.Request.PromptID != "briefing" || first.Request.ProfileID != "" || first.Request.SessionID != "" {
t.Errorf("empty scalar values = %#v", first.Request)
}
wantVars := map[string]string{"": "", "phrase": " two words, three ", "duplicate": "last=value"}
@@ -233,7 +233,7 @@ func TestValidationCommandPreservesPresenceAndRepeatedAssignmentsPerInvocation(t
t.Fatalf("second invocation = %#v, requests=%d", outcome, len(requests))
}
second := requests[1]
if second.Selection.Config.Set || second.Selection.Overlay.Set || second.Request.PromptVersion != "" || second.Request.ProfileID != "" || second.Request.SessionID != "" || len(second.Request.Vars) != 0 || len(second.Request.Inputs) != 0 {
if second.Selection.Config.Set || second.Selection.Overlay.Set || second.Request.PromptID != "briefing" || second.Request.ProfileID != "" || second.Request.SessionID != "" || len(second.Request.Vars) != 0 || len(second.Request.Inputs) != 0 {
t.Errorf("first invocation values leaked into second request: %#v", second)
}
if selectedInputs[1].campaign.Set || selectedInputs[1].campaign.Value != "" {
@@ -256,6 +256,7 @@ func TestValidationCommandUsageAndInputFailuresStopProviderWork(t *testing.T) {
{name: "extra operand", args: []string{"promptkit", "requirements", "validate", "briefing", "extra", "--campaign", "spring"}},
{name: "unsupported format", args: []string{"promptkit", "requirements", "validate", "briefing", "--campaign", "spring", "--format", "text"}},
{name: "unknown flag", args: []string{"promptkit", "requirements", "validate", "briefing", "--campaign", "spring", "--unknown", "value"}},
{name: "removed prompt version flag", args: []string{"promptkit", "requirements", "validate", "briefing", "--campaign", "spring", "--version", "2"}},
{name: "malformed variable before valid assignment", args: []string{"promptkit", "requirements", "validate", "briefing", "--campaign", "spring", "--var", "broken", "--var", "mode=full"}},
{name: "malformed input before valid assignment", args: []string{"promptkit", "requirements", "validate", "briefing", "--campaign", "spring", "--input", "broken", "--input", "notes=file.txt"}},
{name: "missing required selector", args: []string{"promptkit", "requirements", "validate", "briefing"}},