Expose chunk plan provenance and safe diagnostics

This commit is contained in:
2026-07-18 00:45:01 +00:00
parent 8ba5228c01
commit 6fc6ce0adb
15 changed files with 363 additions and 41 deletions

View File

@@ -69,12 +69,42 @@ type RejectedOutputManifest struct {
DiagnosticArtifactPath string `json:"diagnostic_artifact_path,omitempty"`
}
type ChunkPlanManifest struct {
Mode string `json:"mode"`
Action string `json:"action,omitempty"`
SourceDigest string `json:"source_digest,omitempty"`
PlanDigest string `json:"plan_digest,omitempty"`
PlanSchemaVersion string `json:"plan_schema_version,omitempty"`
RequestedModule string `json:"requested_module"`
ProducerInputModule string `json:"producer_input_module,omitempty"`
ProducerModule string `json:"producer_module,omitempty"`
ProducerLLMProfile string `json:"producer_llm_profile,omitempty"`
ProducerReferences []ReferenceProvenance `json:"producer_references,omitempty"`
ProducerMetadata map[string]any `json:"producer_metadata,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
}
// ChunkPlanSummary is deliberately limited to cache and validation decisions.
// It must never contain plan units, source content, annotations, or model I/O.
type ChunkPlanSummary struct {
Mode string `json:"mode"`
SourceDigest string `json:"source_digest,omitempty"`
CandidateDigest string `json:"candidate_digest,omitempty"`
RequestedModule string `json:"requested_module"`
LookupStatus string `json:"lookup_status"`
LookupReason string `json:"lookup_reason,omitempty"`
Action string `json:"action,omitempty"`
ValidationStatus string `json:"validation_status"`
PublicationStatus string `json:"publication_status"`
}
type RunManifest struct {
RunID string `json:"run_id,omitempty"`
PipelineID string `json:"pipeline_id,omitempty"`
PipelineDigest string `json:"pipeline_digest,omitempty"`
InputModule string `json:"input_module,omitempty"`
Chunker string `json:"chunker,omitempty"`
ChunkPlan *ChunkPlanManifest `json:"chunk_plan,omitempty"`
SourceDigests []string `json:"source_digests,omitempty"`
Extractors []string `json:"extractors,omitempty"`
Merger string `json:"merger,omitempty"`

View File

@@ -2,6 +2,7 @@ package artifacts
import (
"encoding/json"
"strings"
"testing"
)
@@ -16,6 +17,37 @@ func TestRunManifestOmitsEmptyOptionalFields(t *testing.T) {
}
}
func TestRunManifestChunkPlanIsAdditiveAndOmitsPlanContent(t *testing.T) {
manifest := RunManifest{ChunkPlan: &ChunkPlanManifest{
Mode: "auto", Action: "reused", SourceDigest: "sha256:source", PlanDigest: "sha256:plan",
PlanSchemaVersion: "notarius.chunk-plan.v1", RequestedModule: "chunk/current",
ProducerInputModule: "input/original", ProducerModule: "chunk/original",
}}
encoded, err := json.Marshal(manifest)
if err != nil {
t.Fatal(err)
}
text := string(encoded)
for _, want := range []string{`"chunk_plan"`, `"action":"reused"`, `"requested_module":"chunk/current"`, `"producer_module":"chunk/original"`} {
if !strings.Contains(text, want) {
t.Fatalf("manifest JSON %s does not contain %s", text, want)
}
}
for _, forbidden := range []string{`"plan"`, `"units"`, `"annotations"`} {
if strings.Contains(text, forbidden) {
t.Fatalf("manifest JSON contains forbidden field %s: %s", forbidden, text)
}
}
var legacy RunManifest
if err := json.Unmarshal([]byte(`{"pipeline_id":"legacy"}`), &legacy); err != nil {
t.Fatal(err)
}
if legacy.PipelineID != "legacy" || legacy.ChunkPlan != nil {
t.Fatalf("legacy manifest = %#v", legacy)
}
}
func TestRunManifestIncludesPipelineAndArtifactLaneFields(t *testing.T) {
manifest := RunManifest{
PipelineID: "pipeline-1",

View File

@@ -8,6 +8,7 @@ const (
ArtifactCheckpointEvents = "checkpoint-events.json"
ArtifactSourceDocument = "source-document.json"
ArtifactRunManifest = "run-manifest.json"
ArtifactChunkPlan = "chunk-plan.json"
ArtifactRunReport = "run-report.json"
ArtifactWarnings = "warnings.json"
ArtifactErrorLog = "error.log"

View File

@@ -10,6 +10,7 @@ func TestArtifactNamesUseExtractionOrientedNames(t *testing.T) {
ArtifactResolvedReferences,
ArtifactSourceDocument,
ArtifactRunManifest,
ArtifactChunkPlan,
ArtifactRunReport,
ArtifactWarnings,
ArtifactErrorLog,

View File

@@ -167,6 +167,10 @@ func (r *RunDirectory) WriteRunManifest(manifest artifacts.RunManifest) error {
return r.WriteJSONArtifact(ArtifactRunManifest, manifest)
}
func (r *RunDirectory) WriteChunkPlan(summary artifacts.ChunkPlanSummary) error {
return r.WriteJSONArtifact(ArtifactChunkPlan, summary)
}
func (r *RunDirectory) WriteRunReport(payload any) error {
return r.WriteJSONArtifact(ArtifactRunReport, payload)
}

View File

@@ -200,6 +200,9 @@ func TestWriteTypedArtifacts(t *testing.T) {
if err := runDir.WriteRunManifest(artifacts.RunManifest{RunID: "run-1"}); err != nil {
t.Fatalf("WriteRunManifest: %v", err)
}
if err := runDir.WriteChunkPlan(artifacts.ChunkPlanSummary{Mode: "auto", RequestedModule: "chunk/test", LookupStatus: "invalid", LookupReason: "stored chunk plan failed validation", ValidationStatus: "not_run", PublicationStatus: "not_published"}); err != nil {
t.Fatalf("WriteChunkPlan: %v", err)
}
if err := runDir.WriteRunReport(map[string]any{"ok": true}); err != nil {
t.Fatalf("WriteRunReport: %v", err)
}
@@ -213,6 +216,7 @@ func TestWriteTypedArtifacts(t *testing.T) {
ArtifactResolvedReferences,
ArtifactSourceDocument,
ArtifactRunManifest,
ArtifactChunkPlan,
ArtifactRunReport,
ArtifactWarnings,
} {
@@ -220,6 +224,15 @@ func TestWriteTypedArtifacts(t *testing.T) {
t.Fatalf("expected artifact %q: %v", name, err)
}
}
chunkSummary, err := os.ReadFile(filepath.Join(runDir.Path(), ArtifactChunkPlan))
if err != nil {
t.Fatal(err)
}
for _, forbidden := range []string{"units", "annotations", "source content", "prompt", "response", "raw invalid"} {
if strings.Contains(string(chunkSummary), forbidden) {
t.Fatalf("chunk plan summary leaked %q: %s", forbidden, chunkSummary)
}
}
}
func TestWriteRedactedEffectiveConfigWritesPayloadReturnedByProvider(t *testing.T) {