indexer_placement reads where modules run as the indexer wrote it, by node or by module (issue 1126, ADR 0549 §2)

This commit is contained in:
2026-10-26 18:15:53 +01:00
parent 1cbfd841de
commit 6aa424fa6d
7 changed files with 99 additions and 4 deletions
@@ -20,6 +20,8 @@ import (
const (
ModuleReadTool = "indexer_module"
ModulesReadTool = "indexer_modules"
// PlacementReadTool reads the indexer's placement (novox/hq ADR 0549 §2, issue 1126).
PlacementReadTool = "indexer_placement"
)
// StateRead reads the indexer's modules state, as the view's reads grant it: its keys and a key's stored value (nil for a key with none).
@@ -39,6 +41,7 @@ type Reader struct {
WantsState StateRead // the assigner's wants and holds
DifferencesState StateRead // the assigner's differences
HavesState StateRead // the node-engine's haves
PlacementState StateRead // the indexer's placement
}
// Module is the stored value of modules.<module>, exactly as the indexer wrote it.
@@ -613,3 +616,55 @@ func (r Reader) Differences(node, module string) (KeptAnswer, error) {
func (r Reader) Haves(node, module string) (KeptAnswer, error) {
return r.kept(r.HavesState, "the haves", node, module)
}
// Placement is the indexer's placement as stored (ADR 0549 §2): one node's key with node, every node placing a module
// with module (each node's key whole, so its pins, ports and bindings are beside the module), and every node's key with
// neither. A key that does not read as a placement is listed as unreadable, never dropped.
func (r Reader) Placement(node, module string) (map[string]any, error) {
node, module = strings.TrimSpace(node), strings.TrimSpace(module)
if r.PlacementState.Keys == nil || r.PlacementState.Get == nil {
return nil, fmt.Errorf("the placement state cannot be read here")
}
if node != "" {
key, err := delivery.PlacementKey(node)
if err != nil {
return nil, err
}
raw, err := r.PlacementState.Get(key)
if err != nil {
return nil, err
}
if len(raw) == 0 {
return nil, fmt.Errorf("no placement of %s: the catalogue holds no placement file for it, or the indexer has read none yet", node)
}
return map[string]any{"placement": map[string]json.RawMessage{node: raw}}, nil
}
keys, err := r.PlacementState.Keys()
if err != nil {
return nil, err
}
sort.Strings(keys)
values, err := r.PlacementState.values(keys)
if err != nil {
return nil, err
}
out := map[string]json.RawMessage{}
var unreadable []string
for i, key := range keys {
n, err := delivery.ParsePlacementKey(key)
if err != nil {
unreadable = append(unreadable, key)
continue
}
p, err := delivery.DecodePlacement(values[i])
if err != nil {
unreadable = append(unreadable, key)
continue
}
if module != "" && !slices.Contains(p.Assigned(), module) {
continue
}
out[n] = values[i]
}
return map[string]any{"placement": out, "unreadable": unreadable}, nil
}