mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-25 11:52:08 +00:00
f8ff5742e5
Claude-Session: https://claude.ai/code/session_01E2fRi12n8rARczvdEa2LYT
48 lines
1.9 KiB
Go
48 lines
1.9 KiB
Go
package items
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"github.com/PerpetualSoftware/pad/internal/models"
|
|
)
|
|
|
|
// UndeclaredOverrideKeys returns the keys in an override map that the target
|
|
// schema does not declare, sorted so a caller's error message is stable for a
|
|
// given input. A nil or empty override map returns nil.
|
|
//
|
|
// It lives HERE, in the package both consumers already import, because two
|
|
// implementations of it is exactly the divergence PLAN-2357's DR-6 exists to
|
|
// prevent (Codex round 17). The cross-workspace copy PREFLIGHT refuses an
|
|
// undeclared override with a 400, and the COPY refuses it in
|
|
// Store.migrateCopyFields; if the two ever disagreed about which keys count as
|
|
// undeclared, the preview would promise something the copy refuses, or worse
|
|
// permit something the copy silently persists as an orphan key.
|
|
//
|
|
// The rule is a straight key-set difference with no exemptions. There is no
|
|
// reserved-key escape hatch in items.fields — PLAN-2357 DR-2 records why the
|
|
// cross-workspace provenance pointer needed its own table rather than a fields
|
|
// key — so every key the schema does not declare is an orphan.
|
|
//
|
|
// Why refusing is the right answer rather than dropping the key silently:
|
|
// ValidateFields ignores keys the schema does not declare, so an undeclared
|
|
// override that is merged is WRITTEN, invisible to every schema-driven
|
|
// surface. Dropping it instead would be no better — a client that asked for a
|
|
// value would get an item without it and no way to tell.
|
|
func UndeclaredOverrideKeys(overrides map[string]any, targetFields []models.FieldDef) []string {
|
|
if len(overrides) == 0 {
|
|
return nil
|
|
}
|
|
declared := make(map[string]struct{}, len(targetFields))
|
|
for _, f := range targetFields {
|
|
declared[f.Key] = struct{}{}
|
|
}
|
|
var bad []string
|
|
for k := range overrides {
|
|
if _, ok := declared[k]; !ok {
|
|
bad = append(bad, k)
|
|
}
|
|
}
|
|
sort.Strings(bad)
|
|
return bad
|
|
}
|