Add canonical chunk plan source model
This commit is contained in:
@@ -242,15 +242,25 @@ func sourceChunksFromEnvelope(values []chunkEnvelope) ([]source.Chunk, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
annotations, err := source.CanonicalizeChunkAnnotations(value.Annotations)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("canonicalize chunk %q annotations: %w", value.ID, err)
|
||||
}
|
||||
planAnnotations, err := source.CanonicalizeChunkAnnotations(value.PlanAnnotations)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("canonicalize chunk %q plan_annotations: %w", value.ID, err)
|
||||
}
|
||||
out = append(out, source.Chunk{
|
||||
ID: value.ID,
|
||||
SourceID: value.SourceID,
|
||||
Index: value.Index,
|
||||
Ref: value.Ref,
|
||||
Content: content,
|
||||
MediaType: value.Content.MediaType,
|
||||
Units: cloneSourceUnits(value.Units),
|
||||
Metadata: cloneMetadata(value.Metadata),
|
||||
ID: value.ID,
|
||||
SourceID: value.SourceID,
|
||||
Index: value.Index,
|
||||
Ref: value.Ref,
|
||||
Content: content,
|
||||
MediaType: value.Content.MediaType,
|
||||
Units: cloneSourceUnits(value.Units),
|
||||
Metadata: cloneMetadata(value.Metadata),
|
||||
Annotations: annotations,
|
||||
PlanAnnotations: planAnnotations,
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
|
||||
@@ -251,13 +251,15 @@ type chunksEnvelope struct {
|
||||
}
|
||||
|
||||
type chunkEnvelope struct {
|
||||
ID string `json:"id"`
|
||||
SourceID string `json:"source_id"`
|
||||
Index int `json:"index"`
|
||||
Ref source.SourceRef `json:"ref"`
|
||||
Content binaryEnvelope `json:"content"`
|
||||
Units []source.SourceUnit `json:"units,omitempty"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
ID string `json:"id"`
|
||||
SourceID string `json:"source_id"`
|
||||
Index int `json:"index"`
|
||||
Ref source.SourceRef `json:"ref"`
|
||||
Content binaryEnvelope `json:"content"`
|
||||
Units []source.SourceUnit `json:"units,omitempty"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
Annotations source.ChunkAnnotations `json:"annotations,omitempty"`
|
||||
PlanAnnotations source.ChunkAnnotations `json:"plan_annotations,omitempty"`
|
||||
}
|
||||
|
||||
type binaryEnvelope struct {
|
||||
@@ -320,13 +322,15 @@ func chunkEnvelopes(chunks []source.Chunk) []chunkEnvelope {
|
||||
out := make([]chunkEnvelope, 0, len(chunks))
|
||||
for _, chunk := range chunks {
|
||||
out = append(out, chunkEnvelope{
|
||||
ID: chunk.ID,
|
||||
SourceID: chunk.SourceID,
|
||||
Index: chunk.Index,
|
||||
Ref: chunk.Ref,
|
||||
Content: binaryEnvelopeFromContent(chunk.Content, chunk.MediaType, chunk.Metadata, nil),
|
||||
Units: cloneSourceUnits(chunk.Units),
|
||||
Metadata: cloneMetadata(chunk.Metadata),
|
||||
ID: chunk.ID,
|
||||
SourceID: chunk.SourceID,
|
||||
Index: chunk.Index,
|
||||
Ref: chunk.Ref,
|
||||
Content: binaryEnvelopeFromContent(chunk.Content, chunk.MediaType, chunk.Metadata, nil),
|
||||
Units: cloneSourceUnits(chunk.Units),
|
||||
Metadata: cloneMetadata(chunk.Metadata),
|
||||
Annotations: source.CloneChunkAnnotations(chunk.Annotations),
|
||||
PlanAnnotations: source.CloneChunkAnnotations(chunk.PlanAnnotations),
|
||||
})
|
||||
}
|
||||
return out
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -33,6 +34,12 @@ func TestWorkspaceRecorderWritesSuccessfulCheckpointFiles(t *testing.T) {
|
||||
Content: []byte("chunk content"),
|
||||
MediaType: "text/plain",
|
||||
Units: doc.Units,
|
||||
Annotations: source.ChunkAnnotations{
|
||||
"same": json.RawMessage(`{"range":1}`),
|
||||
},
|
||||
PlanAnnotations: source.ChunkAnnotations{
|
||||
"same": json.RawMessage(`{"plan":2}`),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -57,7 +64,9 @@ func TestWorkspaceRecorderWritesSuccessfulCheckpointFiles(t *testing.T) {
|
||||
assertManifestStatus(t, filepath.Join(root, "chunk", "manifest.json"), coreworkspace.StatusSucceeded)
|
||||
var chunkPayload struct {
|
||||
Chunks []struct {
|
||||
Content struct {
|
||||
Annotations source.ChunkAnnotations `json:"annotations"`
|
||||
PlanAnnotations source.ChunkAnnotations `json:"plan_annotations"`
|
||||
Content struct {
|
||||
ContentBase64 string `json:"content_base64"`
|
||||
ContentDigest string `json:"content_digest"`
|
||||
} `json:"content"`
|
||||
@@ -77,6 +86,28 @@ func TestWorkspaceRecorderWritesSuccessfulCheckpointFiles(t *testing.T) {
|
||||
if got, want := chunkPayload.Chunks[0].Content.ContentDigest, contentDigest([]byte("chunk content")); got != want {
|
||||
t.Fatalf("content digest = %q, want %q", got, want)
|
||||
}
|
||||
if !jsonEqual(t, chunkPayload.Chunks[0].Annotations["same"], json.RawMessage(`{"range":1}`)) || !jsonEqual(t, chunkPayload.Chunks[0].PlanAnnotations["same"], json.RawMessage(`{"plan":2}`)) {
|
||||
t.Fatalf("checkpoint annotations = %s / %s", chunkPayload.Chunks[0].Annotations["same"], chunkPayload.Chunks[0].PlanAnnotations["same"])
|
||||
}
|
||||
loaded, decision := (&WorkspaceLoader{root: root}).Chunk("generic", doc.Digest)
|
||||
if !decision.Reused || len(loaded.Chunks) != 1 {
|
||||
t.Fatalf("loaded chunk checkpoint = %#v, decision = %#v", loaded, decision)
|
||||
}
|
||||
if string(loaded.Chunks[0].Annotations["same"]) != `{"range":1}` || string(loaded.Chunks[0].PlanAnnotations["same"]) != `{"plan":2}` {
|
||||
t.Fatalf("loaded canonical annotations = %s / %s", loaded.Chunks[0].Annotations["same"], loaded.Chunks[0].PlanAnnotations["same"])
|
||||
}
|
||||
}
|
||||
|
||||
func jsonEqual(t *testing.T, left, right []byte) bool {
|
||||
t.Helper()
|
||||
var leftValue, rightValue any
|
||||
if err := json.Unmarshal(left, &leftValue); err != nil {
|
||||
t.Fatalf("decode left JSON: %v", err)
|
||||
}
|
||||
if err := json.Unmarshal(right, &rightValue); err != nil {
|
||||
t.Fatalf("decode right JSON: %v", err)
|
||||
}
|
||||
return reflect.DeepEqual(leftValue, rightValue)
|
||||
}
|
||||
|
||||
func TestWorkspaceArtifactCheckpointsRoundTripCodecIdentityAndBytes(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user