Bind all canvas element identities to their snapshot owners
This commit is contained in:
@@ -20,6 +20,7 @@ func writeCanvasModelStream(ctx context.Context, w http.ResponseWriter, model ca
|
||||
view := model.view(nil)
|
||||
// A count covers direct members only; an empty canvas is explicit too.
|
||||
counts := map[string]int{}
|
||||
owners := map[string]string{}
|
||||
for _, canvas := range view.Canvases {
|
||||
counts[canvas.ID] = 0
|
||||
}
|
||||
@@ -29,11 +30,17 @@ func writeCanvasModelStream(ctx context.Context, w http.ResponseWriter, model ca
|
||||
reject(w, 500, "invalid_model_coverage")
|
||||
return
|
||||
}
|
||||
id := modelString(element["id"])
|
||||
if id == "" || owners[id] != "" {
|
||||
reject(w, 500, "invalid_model_identity")
|
||||
return
|
||||
}
|
||||
owners[id] = owner
|
||||
counts[owner]++
|
||||
}
|
||||
header := response{"streamVersion": 1, "kind": "header", "sequence": 0, "snapshotToken": token,
|
||||
"documentFormatVersion": canvasModelFormat, "projectId": model.Root, "canvases": view.Canvases,
|
||||
"versions": view.Versions, "tombstones": view.Tombstones, "removedCanvasIds": view.Removed, "elementCount": len(view.Elements), "canvasElementCounts": counts}
|
||||
"versions": view.Versions, "tombstones": view.Tombstones, "removedCanvasIds": view.Removed, "elementCount": len(view.Elements), "canvasElementCounts": counts, "elementOwners": owners}
|
||||
const maxLine = 8 * 1024 * 1024
|
||||
const maxBytes = 64 * 1024 * 1024
|
||||
sequence, total := 0, 0
|
||||
|
||||
@@ -46,6 +46,15 @@ func TestCanonicalModelStreamFramesAndSnapshot(t *testing.T) {
|
||||
if !uuidSlugPattern.MatchString(token) || records[0]["projectId"] != modelRootID || !modelEqual(records[0]["versions"], model.Versions) || !modelEqual(records[0]["tombstones"], model.Tombstones) {
|
||||
t.Fatal("stream header lost snapshot identity")
|
||||
}
|
||||
owners := records[0]["elementOwners"].(map[string]any)
|
||||
if len(owners) != len(model.Elements) {
|
||||
t.Fatal("identity manifest must cover unloaded elements too")
|
||||
}
|
||||
for id, element := range model.Elements {
|
||||
if owners[id] != element["ownerCanvasId"] {
|
||||
t.Fatal("identity manifest mixed owners", id)
|
||||
}
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for i, record := range records {
|
||||
if record["sequence"] != float64(i) || record["snapshotToken"] != token || record["streamVersion"] != float64(1) {
|
||||
@@ -114,3 +123,15 @@ func TestCanonicalModelStreamDeclaresDirectScopeCountsIncludingEmptyCanvases(t *
|
||||
t.Fatal("invalid owner must fail before publishing a scope certificate")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalModelStreamRejectsAmbiguousIdentityManifest(t *testing.T) {
|
||||
for _, id := range []string{"", "a"} {
|
||||
model := modelTestDocument()
|
||||
model.Elements["other"] = modelTestNode(id, modelRootID)
|
||||
writer := httptest.NewRecorder()
|
||||
writeCanvasModelStream(context.Background(), writer, model)
|
||||
if writer.Code != 500 || writer.Flushed {
|
||||
t.Fatal("ambiguous identity was published before rejection")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user