Derive stable prompt sessions for CLI runs
This commit is contained in:
@@ -153,10 +153,10 @@ func runPipelineCommand(args []string, stdout, stderr io.Writer, opts Options) i
|
||||
resume := fs.Bool("resume", false, "reuse compatible recorded checkpoints")
|
||||
recomputeStep := singleValueFlag{name: "--recompute-step"}
|
||||
chunkCache := chunkCacheFlag{}
|
||||
sessionID := sessionIDFlag{}
|
||||
requestedSessionID := sessionIDFlag{}
|
||||
referenceFlags := stringListFlag{}
|
||||
withoutReferenceFlags := stringListFlag{}
|
||||
fs.Var(&sessionID, "session-id", "prompt session identifier")
|
||||
fs.Var(&requestedSessionID, "session-id", "prompt session identifier")
|
||||
fs.Var(&reasoningEffort, "reasoning-effort", "reasoning effort override")
|
||||
fs.Var(&chunkCache, "chunk_cache", "chunk plan cache mode: auto, bypass, or refresh")
|
||||
fs.Var(&referenceFlags, "reference", "reference binding, as slot=path, chunk.slot=path, merge.slot=path, lane.slot=path, lane.extract.slot=path, lane.merge.slot=path, or lane.normalize.slot=path")
|
||||
@@ -199,7 +199,7 @@ func runPipelineCommand(args []string, stdout, stderr io.Writer, opts Options) i
|
||||
fmt.Fprintln(stderr, "notarius: --debug-dir must not be empty")
|
||||
return 2
|
||||
}
|
||||
if sessionID.set && strings.TrimSpace(sessionID.value) == "" {
|
||||
if requestedSessionID.set && strings.TrimSpace(requestedSessionID.value) == "" {
|
||||
fmt.Fprintln(stderr, "notarius: --session-id must not be empty")
|
||||
return 2
|
||||
}
|
||||
@@ -414,11 +414,15 @@ func runPipelineCommand(args []string, stdout, stderr io.Writer, opts Options) i
|
||||
if err != nil {
|
||||
return failPipelineCommand(stderr, commandState, terminalWriter, fmt.Errorf("read input %q: %w", strings.TrimSpace(*inputPath), err))
|
||||
}
|
||||
effectiveSessionID, err := resolvePromptSessionID(requestedSessionID.value, effective.ResolvedPipeline.Input.Module, rawInput)
|
||||
if err != nil {
|
||||
return failPipelineCommand(stderr, commandState, terminalWriter, err)
|
||||
}
|
||||
chunkPlans, err := chunkPlanStoreForRun(effective.Config.Cache.ChunkPlans, opts)
|
||||
if err != nil {
|
||||
return failPipelineCommand(stderr, commandState, terminalWriter, err)
|
||||
}
|
||||
checkpointRecorder, checkpointLoader, err := checkpointHandlersForRun(effective.Config.Cache.Checkpoints, opts, effective.ResolvedPipeline, prepared.CheckpointFingerprints(), llmFingerprints, rawInput, only, llmProfiles, strings.TrimSpace(*llmProfile), strings.TrimSpace(sessionID.value), runtimeOverrides, *resume)
|
||||
checkpointRecorder, checkpointLoader, err := checkpointHandlersForRun(effective.Config.Cache.Checkpoints, opts, effective.ResolvedPipeline, prepared.CheckpointFingerprints(), llmFingerprints, rawInput, only, llmProfiles, strings.TrimSpace(*llmProfile), effectiveSessionID, runtimeOverrides, *resume)
|
||||
if err != nil {
|
||||
return failPipelineCommand(stderr, commandState, terminalWriter, err)
|
||||
}
|
||||
@@ -427,7 +431,7 @@ func runPipelineCommand(args []string, stdout, stderr io.Writer, opts Options) i
|
||||
Prepared: prepared,
|
||||
Path: strings.TrimSpace(*inputPath),
|
||||
RawInput: rawInput,
|
||||
SessionID: strings.TrimSpace(sessionID.value),
|
||||
SessionID: effectiveSessionID,
|
||||
RunID: runID,
|
||||
StartedAt: startedAt,
|
||||
LLMProfiles: llmProfiles,
|
||||
|
||||
@@ -492,17 +492,28 @@ func TestEffectiveLLMProfileIDsAreSortedDeduplicatedAndLLMOnly(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSessionIDUsesExplicitValueOrSourceDocumentID(t *testing.T) {
|
||||
func TestRunSessionIDUsesEffectiveValueForPromptRequests(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{name: "source document", want: "source"},
|
||||
{name: "derived default"},
|
||||
{name: "explicit trimmed value", args: []string{"--session-id", " explicit-session "}, want: "explicit-session"},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
roots := newStateTestRoots(t)
|
||||
want := tt.want
|
||||
if want == "" {
|
||||
rawInput, err := os.ReadFile(roots.input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want, err = resolvePromptSessionID("", "test/input", rawInput)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
harness := newStateTestHarness()
|
||||
args := append([]string{"run", "sample", "--config", roots.config, "--input", roots.input, "--chunk_cache", "bypass"}, tt.args...)
|
||||
var stdout, stderr bytes.Buffer
|
||||
@@ -517,8 +528,8 @@ func TestRunSessionIDUsesExplicitValueOrSourceDocumentID(t *testing.T) {
|
||||
t.Fatalf("session IDs = %#v, want all prompt-facing module requests", sessions)
|
||||
}
|
||||
for _, session := range sessions {
|
||||
if session != tt.want {
|
||||
t.Fatalf("session IDs = %#v, want %q", sessions, tt.want)
|
||||
if session != want {
|
||||
t.Fatalf("session IDs = %#v, want %q", sessions, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
26
internal/cli/session.go
Normal file
26
internal/cli/session.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const generatedSessionIDPrefix = "notarius:v1:"
|
||||
|
||||
func resolvePromptSessionID(explicitSessionID, inputModule string, rawInput []byte) (string, error) {
|
||||
inputModule = strings.TrimSpace(inputModule)
|
||||
if inputModule == "" {
|
||||
return "", fmt.Errorf("resolve prompt session: input module key must not be empty")
|
||||
}
|
||||
if sessionID := strings.TrimSpace(explicitSessionID); sessionID != "" {
|
||||
return sessionID, nil
|
||||
}
|
||||
|
||||
hasher := sha256.New()
|
||||
_, _ = hasher.Write([]byte(inputModule))
|
||||
_, _ = hasher.Write([]byte{0})
|
||||
_, _ = hasher.Write(rawInput)
|
||||
return generatedSessionIDPrefix + hex.EncodeToString(hasher.Sum(nil)), nil
|
||||
}
|
||||
61
internal/cli/session_test.go
Normal file
61
internal/cli/session_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package cli
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestResolvePromptSessionIDUsesVersionedInputIdentity(t *testing.T) {
|
||||
got, err := resolvePromptSessionID("", " seriatim/input/transcript ", []byte("{\"entries\":[\"one\"]}\n"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
const want = "notarius:v1:e15fefdca48653e73248b4157900547be1fd34250c0138f8d3d1f2e89b43bb25"
|
||||
if got != want {
|
||||
t.Fatalf("resolved session = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvePromptSessionIDStabilityAndOverride(t *testing.T) {
|
||||
rawInput := []byte("same input")
|
||||
baseline, err := resolvePromptSessionID("", "input/transcript", rawInput)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
repeated, err := resolvePromptSessionID("", "input/transcript", rawInput)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if baseline != repeated {
|
||||
t.Fatalf("resolved sessions = %q and %q, want stable value", baseline, repeated)
|
||||
}
|
||||
|
||||
differentModule, err := resolvePromptSessionID("", "input/other", rawInput)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if baseline == differentModule {
|
||||
t.Fatalf("resolved sessions = %q for distinct input modules", baseline)
|
||||
}
|
||||
|
||||
differentInput, err := resolvePromptSessionID("", "input/transcript", []byte("same inpuu"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if baseline == differentInput {
|
||||
t.Fatalf("resolved sessions = %q for distinct input bytes", baseline)
|
||||
}
|
||||
|
||||
override, err := resolvePromptSessionID(" explicit-session ", "input/other", []byte("different input"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if override != "explicit-session" {
|
||||
t.Fatalf("resolved override = %q, want %q", override, "explicit-session")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvePromptSessionIDRejectsEmptyInputModule(t *testing.T) {
|
||||
for _, explicitSessionID := range []string{"", "explicit-session"} {
|
||||
if _, err := resolvePromptSessionID(explicitSessionID, " \t", []byte("input")); err == nil {
|
||||
t.Fatalf("resolvePromptSessionID(%q) error = nil, want empty module failure", explicitSessionID)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,7 +191,7 @@ func (r *Runner) Run(ctx context.Context, input RunInput) (output RunOutput, err
|
||||
return failOutput(output), fmt.Errorf("write source debug artifact: %w", err)
|
||||
}
|
||||
sourceInput := sourceInputMaterial(input.Path, input.RawInput)
|
||||
sessionID := resolvedSessionID(input.SessionID, doc.ID)
|
||||
sessionID := strings.TrimSpace(input.SessionID)
|
||||
output.Manifest.Metadata, err = manifestMetadataWithSessionID(output.Manifest.Metadata, sessionID)
|
||||
if err != nil {
|
||||
return failOutput(output), err
|
||||
@@ -896,13 +896,6 @@ func sourceInputOriginURI(inputPath string) string {
|
||||
return fileURI(inputPath)
|
||||
}
|
||||
|
||||
func resolvedSessionID(explicit string, sourceDocumentID string) string {
|
||||
if trimmed := strings.TrimSpace(explicit); trimmed != "" {
|
||||
return trimmed
|
||||
}
|
||||
return strings.TrimSpace(sourceDocumentID)
|
||||
}
|
||||
|
||||
func manifestMetadataWithSessionID(metadata map[string]any, sessionID string) (map[string]any, error) {
|
||||
out, err := cloneMetadata(metadata)
|
||||
if err != nil {
|
||||
|
||||
37
internal/framework/pipeline/runner_session_test.go
Normal file
37
internal/framework/pipeline/runner_session_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package pipeline
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRunnerUsesProvidedSessionWithoutSourceFallback(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
session string
|
||||
wantSet bool
|
||||
want string
|
||||
}{
|
||||
{name: "provided", session: " supplied-session ", wantSet: true, want: "supplied-session"},
|
||||
{name: "empty", wantSet: false},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
prepared, _ := preparedTerminalDebugPipeline(t)
|
||||
output, err := New().Run(context.Background(), RunInput{
|
||||
Prepared: prepared,
|
||||
RawInput: []byte("input"),
|
||||
SessionID: test.session,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
value, found := output.Manifest.Metadata["session_id"]
|
||||
if found != test.wantSet {
|
||||
t.Fatalf("manifest session presence = %t, want %t; metadata = %#v", found, test.wantSet, output.Manifest.Metadata)
|
||||
}
|
||||
if found && value != test.want {
|
||||
t.Fatalf("manifest session = %#v, want %q", value, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user