Bind all canvas element identities to their snapshot owners
Foundry Go checks / check (push) Successful in 2m53s
Foundry Go checks / check (pull_request) Successful in 2m55s

This commit is contained in:
2026-11-09 04:11:26 +08:00
parent ad6ab30b5f
commit 9cf6e4cedf
2 changed files with 29 additions and 1 deletions
+8 -1
View File
@@ -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")
}
}
}