node-engine client: the exported fingerprint key part is mesh-catalog's client's, the codec checks the field's form itself (a client imports no other seat's client; ADR 0625 §4, ADR 0634 §2; issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 15:05:08 +01:00
parent cd2615f094
commit 25da1f8879
2 changed files with 12 additions and 11 deletions
+11 -10
View File
@@ -20,16 +20,18 @@ import (
// alone, so the encoding keeps the field order its first writer used, which the contract's generated type (fields sorted)
// does not: the wire structs below hold it.
// SourcePrefix is the start of a fingerprint of the new form, `src2:` and 64 lowercase hex digits.
const SourcePrefix = "src2:"
// sourcePrefix opens a source fingerprint of the form mesh-catalog's client defines (meshcatalogclient.SourcePrefix2,
// KeyPart); a client imports no other seat's client (ADR 0625 §4), so a composition's field is checked here for that form
// only, and a caller makes the key part with meshcatalogclient.KeyPart.
const sourcePrefix = "src2:"
// FingerprintKeyPart is a new-form fingerprint as the key part of ComposedKey: its 64 hex digits without the prefix.
func FingerprintKeyPart(fp string) (string, error) {
part, ok := strings.CutPrefix(fp, SourcePrefix)
// checkFingerprintForm refuses a fingerprint that is not `src2:` and 64 lowercase hex digits, naming it.
func checkFingerprintForm(fp string) error {
part, ok := strings.CutPrefix(fp, sourcePrefix)
if !ok || !keys.IsHex64(part) {
return "", fmt.Errorf("node-engine: %q is not a fingerprint of the new form, %s and 64 lowercase hex digits", fp, SourcePrefix)
return fmt.Errorf("node-engine: %q is not a fingerprint of the new form, %s and 64 lowercase hex digits", fp, sourcePrefix)
}
return part, nil
return nil
}
// Composition is one module's resources composed for one node at one fingerprint, or, with Module and FP empty, the node's
@@ -171,11 +173,10 @@ func (c Composition) check() error {
return fmt.Errorf("node-engine: the node's own composition carries no plan and no refusal: only a module's does")
}
} else {
part, err := FingerprintKeyPart(c.FP)
if err != nil {
if err := checkFingerprintForm(c.FP); err != nil {
return err
}
if _, err := ComposedKey(c.Node, c.Module, part); err != nil {
if _, err := ComposedKey(c.Node, c.Module, strings.TrimPrefix(c.FP, sourcePrefix)); err != nil {
return err
}
if !ownIsEmpty(c.Own) {
+1 -1
View File
@@ -36,7 +36,7 @@ func captured(t *testing.T) (account string, byModule map[string][]json.RawMessa
return c.Account, byModule
}
func fp(n int) string { return SourcePrefix + fmt.Sprintf("%064x", n) }
func fp(n int) string { return sourcePrefix + fmt.Sprintf("%064x", n) }
// TestCompositionDigests: a composition's digest is the one the first writer computed (the values are the digests
// mesh-sdk's delivery package gave for the same compositions when it moved here), whatever order the contract's generated