// Package intent implements the FLUID intent store. // // ArchitectureBlueprint.md section 27: the system must retain the exact // interface evolution intent governing each revision, so that a later audit can // answer whether a change was valid under the intent that existed when it was // made. An intent document is therefore versioned, content-addressed and // immutable once recorded. package intent import ( "context" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "fmt" "regexp" "strings" "time" "github.com/tegwick/fluid-core/internal/contract" "github.com/tegwick/fluid-core/internal/evidence" ) // AuthorityMode is the operational authority granted to the Daimon. // // FluidAPIStandards.md section 26 is explicit that these describe authority, // not maturity: a high-assurance system may deliberately remain at FLUID-2. type AuthorityMode int const ( ModeInstrumented AuthorityMode = iota // FLUID-0 ModeAnalytical // FLUID-1 ModeAdvisory // FLUID-2 ModeConstructive // FLUID-3 ModeExperimental // FLUID-4 ModeBoundedAutonomous // FLUID-5 ModeEvolutionary // FLUID-6 ) // String renders the canonical FLUID-N form. func (m AuthorityMode) String() string { return fmt.Sprintf("FLUID-%d", int(m)) } // Valid reports whether m is a defined mode. func (m AuthorityMode) Valid() bool { return m >= ModeInstrumented && m <= ModeEvolutionary } // Allows reports whether this mode permits at least the authority of want. func (m AuthorityMode) Allows(want AuthorityMode) bool { return m >= want } var modePattern = regexp.MustCompile(`FLUID-([0-6])\b`) // Version is one recorded interface evolution intent. type Version struct { // Version is the human-facing label, such as "IEI-7". Version string `json:"version"` // Digest content-addresses the document. Two intent versions with the same // digest are the same intent, whatever they were called. Digest contract.Digest `json:"digest"` // Document is the full text, retained verbatim. A summary would not settle // an audit question about what the intent actually said. Document string `json:"document"` // Mode is the operational authority the document declares. Mode AuthorityMode `json:"mode"` // RecordedAt is when this version entered the store. RecordedAt time.Time `json:"recorded_at"` } // Store holds intent versions and which one is currently active. type Store struct { ev evidence.Store iface contract.InterfaceID } // New returns a store backed by ev. func New(ev evidence.Store, iface contract.InterfaceID) *Store { return &Store{ev: ev, iface: iface} } var ( // ErrNotFound reports an unknown intent version. ErrNotFound = evidence.ErrNotFound // ErrImmutable reports an attempt to change a recorded intent version. ErrImmutable = errors.New("a recorded intent version cannot be changed") // ErrNoActive reports that no intent has been made active yet. ErrNoActive = errors.New("no active interface evolution intent") ) // activeKey is the record id holding the active-version pointer. const activeKey = "__active__" // Digest computes the content address of an intent document. func Digest(document string) contract.Digest { sum := sha256.Sum256([]byte(document)) return contract.Digest("sha256:" + hex.EncodeToString(sum[:])) } // ParseMode extracts the declared authority mode from an intent document. // // The template writes the mode as a fenced list of alternatives before it is // filled in; a document that still contains every mode has not been completed, // and defaulting it to something permissive would be exactly the wrong failure. func ParseMode(document string) (AuthorityMode, error) { matches := modePattern.FindAllStringSubmatch(document, -1) if len(matches) == 0 { return 0, errors.New("intent document declares no FLUID-N authority mode") } seen := map[string]bool{} for _, m := range matches { seen[m[1]] = true } if len(seen) > 1 { return 0, fmt.Errorf( "intent document declares %d different authority modes; the template placeholder has not been resolved", len(seen)) } return AuthorityMode(matches[0][1][0] - '0'), nil } // Put records an intent version. // // Recording the same version twice with identical content is a no-op, which // makes startup idempotent. Recording it with different content is refused: // changing what a revision was governed by, after the fact, would break the // audit question the store exists to answer. func (s *Store) Put(ctx context.Context, version, document string) (Version, error) { if version == "" { return Version{}, errors.New("intent version label is required") } mode, err := ParseMode(document) if err != nil { return Version{}, fmt.Errorf("intent %s: %w", version, err) } v := Version{ Version: version, Digest: Digest(document), Document: document, Mode: mode, RecordedAt: time.Now().UTC(), } if existing, err := s.Get(ctx, version); err == nil { if existing.Digest != v.Digest { return Version{}, fmt.Errorf("%w: %s already recorded with digest %s", ErrImmutable, version, existing.Digest) } return existing, nil } else if !errors.Is(err, ErrNotFound) { return Version{}, err } body, err := json.Marshal(v) if err != nil { return Version{}, err } if err := s.ev.PutRecord(ctx, contract.KindIntent, s.key(version), body); err != nil { return Version{}, err } if err := s.ev.AppendEvent(ctx, contract.FluidEvent{ SchemaVersion: "0.1", ID: contract.EventID("EV-intent-" + string(v.Digest[7:19])), OccurredAt: v.RecordedAt, EntityType: contract.FluidEventEntityTypeIntent, EntityID: version, EventType: "INTENT_RECORDED", Actor: contract.Actor{Type: contract.ActorTypeSystem, ID: "fluid-intent-store"}, Reason: fmt.Sprintf("recorded %s at authority mode %s", version, mode), }); err != nil { return Version{}, err } return v, nil } // Get returns a recorded intent version. func (s *Store) Get(ctx context.Context, version string) (Version, error) { body, err := s.ev.Record(ctx, contract.KindIntent, s.key(version)) if err != nil { return Version{}, err } var v Version if err := json.Unmarshal(body, &v); err != nil { return Version{}, fmt.Errorf("decode intent %s: %w", version, err) } return v, nil } // SetActive makes a recorded version the governing intent. func (s *Store) SetActive(ctx context.Context, version string) error { v, err := s.Get(ctx, version) if err != nil { return fmt.Errorf("cannot activate %s: %w", version, err) } body, _ := json.Marshal(map[string]string{"version": v.Version, "digest": string(v.Digest)}) if err := s.ev.PutRecord(ctx, contract.KindIntent, s.key(activeKey), body); err != nil { return err } return s.ev.AppendEvent(ctx, contract.FluidEvent{ SchemaVersion: "0.1", ID: contract.EventID("EV-intent-active-" + string(v.Digest[7:19])), OccurredAt: time.Now().UTC(), EntityType: contract.FluidEventEntityTypeIntent, EntityID: version, EventType: "INTENT_ACTIVATED", Actor: contract.Actor{Type: contract.ActorTypeSystem, ID: "fluid-intent-store"}, Reason: fmt.Sprintf("%s is now the governing intent", version), }) } // Active returns the currently governing intent version. func (s *Store) Active(ctx context.Context) (Version, error) { body, err := s.ev.Record(ctx, contract.KindIntent, s.key(activeKey)) if errors.Is(err, ErrNotFound) { return Version{}, ErrNoActive } if err != nil { return Version{}, err } var ptr struct { Version string `json:"version"` } if err := json.Unmarshal(body, &ptr); err != nil { return Version{}, err } return s.Get(ctx, ptr.Version) } // Bind records that a revision is governed by an intent version. // // This is the link Blueprint section 27 requires. It is stored as an event // rather than as a mutable field so that a revision's governing intent cannot // be quietly reassigned later. func (s *Store) Bind(ctx context.Context, rev contract.RevisionID, version string) error { v, err := s.Get(ctx, version) if err != nil { return fmt.Errorf("cannot bind %s: %w", rev, err) } return s.ev.AppendEvent(ctx, contract.FluidEvent{ SchemaVersion: "0.1", ID: contract.EventID("EV-bind-" + string(rev) + "-" + version), OccurredAt: time.Now().UTC(), EntityType: contract.FluidEventEntityTypeRevision, EntityID: string(rev), EventType: "INTENT_BOUND", Actor: contract.Actor{Type: contract.ActorTypeSystem, ID: "fluid-intent-store"}, Inputs: []string{version}, Reason: fmt.Sprintf("%s is governed by %s (%s)", rev, version, v.Digest), }) } // GoverningIntent returns the intent version a revision was bound to. func (s *Store) GoverningIntent(ctx context.Context, rev contract.RevisionID) (Version, error) { events, err := s.ev.Events(ctx, evidence.EventFilter{ EntityType: contract.KindRevision, EntityID: string(rev), EventType: "INTENT_BOUND", }) if err != nil { return Version{}, err } if len(events) == 0 { return Version{}, fmt.Errorf("%w: no intent bound to %s", ErrNotFound, rev) } // The first binding governs. A later one would be a reassignment, which the // audit trail records but must not silently win. first := events[0] if len(first.Inputs) == 0 { return Version{}, fmt.Errorf("binding event %s names no intent version", first.ID) } return s.Get(ctx, first.Inputs[0]) } func (s *Store) key(id string) string { return string(s.iface) + "/" + strings.TrimSpace(id) }