package suite import ( "context" "crypto/ed25519" "net/http" "strings" "testing" "github.com/tegwick/fluid-core/internal/contract" "github.com/tegwick/fluid-core/internal/evidence" "github.com/tegwick/fluid-core/internal/publish" "github.com/tegwick/fluid-core/internal/runtime" "github.com/tegwick/fluid-core/internal/signing" ) // --------------------------------------------------------------------------- // FluidAPIStandards.md section 36 — minimal v0.1 conformance // // An implementation claiming core conformance MUST provide all seven of these. // They are asserted here rather than claimed in a README, because a conformance // claim nobody re-checks is a conformance claim that quietly stops being true. // --------------------------------------------------------------------------- // 1. A deterministic API contract. func TestConformance01_DeterministicContract(t *testing.T) { h := New(t) // The contract is enforced, not merely declared: an undeclared path is // refused rather than proxied through. mustStatus(t, h.Call("c-1", http.MethodGet, "/v1/entries"), http.StatusOK, "declared path") mustStatus(t, h.Call("c-1", http.MethodGet, "/v1/undeclared"), http.StatusBadRequest, "undeclared path") mustStatus(t, h.Call("c-1", http.MethodDelete, "/v1/entries"), http.StatusBadRequest, "undeclared method") // Determinism: the same request must produce the same outcome every time. first := h.Call("c-1", http.MethodGet, "/v1/entries") for i := 0; i < 20; i++ { again := h.Call("c-1", http.MethodGet, "/v1/entries") if again.Code != first.Code || again.Body.String() != first.Body.String() { t.Fatal("identical requests produced different responses") } } } // 2. An identifiable active interface revision. func TestConformance02_IdentifiableRevision(t *testing.T) { h := New(t) rec := h.Call("c-1", http.MethodGet, "/v1/entries") if got := rec.Header().Get("X-FLUID-Revision"); got != "R-1" { t.Errorf("response does not identify its revision: %q", got) } // The revision that served a request must also be identifiable afterwards, // from evidence rather than from the response the consumer happened to keep. h.Drain() for _, ev := range h.Telemetry() { if ev.Revision == nil || *ev.Revision == "" { t.Fatal("a telemetry event does not name the revision that served it") } } } // 3. Interface telemetry. func TestConformance03_Telemetry(t *testing.T) { h := New(t) for i := 0; i < 5; i++ { h.Call("c-1", http.MethodGet, "/v1/entries") } h.Call("c-1", http.MethodGet, "/v1/undeclared") h.Drain() rows := h.Telemetry() if len(rows) != 6 { t.Fatalf("recorded %d telemetry events, want 6", len(rows)) } var errors int for _, ev := range rows { if ev.Kind == contract.FluidTelemetryKindError { errors++ } if ev.Resolution == nil { t.Error("an event does not record why its revision was chosen") } } if errors != 1 { t.Errorf("recorded %d error events, want 1", errors) } } // 4. Declared interface evolutionary intent. func TestConformance04_DeclaredIntent(t *testing.T) { h := New(t) ctx := context.Background() active, err := h.Intents.Active(ctx) if err != nil { t.Fatalf("no active intent: %v", err) } if !active.Mode.Valid() { t.Errorf("active intent declares no valid authority mode") } // Every revision must be bound to the intent version governing it. for _, rev := range []contract.RevisionID{"R-1", "R-2"} { governing, err := h.Intents.GoverningIntent(ctx, rev) if err != nil { t.Errorf("%s has no governing intent: %v", rev, err) continue } if governing.Digest == "" { t.Errorf("%s is bound to an intent with no digest", rev) } } } // 5. An auditable link from evidence to proposed change. func TestConformance05_EvidenceToChangeIsAuditable(t *testing.T) { h := New(t) ctx := context.Background() loop := RunFullLoop(t, h) // Pressure -> hypothesis -> revision -> experiment -> decision must be // traversable without guessing. pressure, err := h.Store.Record(ctx, contract.KindPressure, string(loop.Pressure)) if err != nil { t.Fatalf("pressure not retrievable: %v", err) } if len(pressure) == 0 { t.Fatal("pressure record is empty") } hyp, err := h.Hypotheses.Get(ctx, loop.Hypothesis) if err != nil { t.Fatal(err) } if len(hyp.Observation.EvidenceRefs) == 0 { t.Error("the hypothesis cites no evidence") } if len(hyp.CandidateRevisionRefs) == 0 { t.Error("the hypothesis names no candidate revision") } if len(hyp.ExperimentRefs) == 0 { t.Error("the hypothesis names no experiment") } // And the revision must carry events reaching back to the decision. events := h.Events(string(loop.Candidate)) var sawDecision bool for _, ev := range events { if strings.HasPrefix(ev.EventType, "PROMOTION_DECIDED") { sawDecision = true if len(ev.Inputs) == 0 { t.Error("the promotion decision cites no inputs") } } } if !sawDecision { t.Error("no promotion decision is recorded against the candidate") } } // 6. Explicit responsibility boundaries. func TestConformance06_ResponsibilityBoundaries(t *testing.T) { h := New(t) // The interface owns representation and routing; the backend owns the data. // When the backend is gone, the interface must report that rather than // inventing a response. h.StopAdapter("R-1") rec := h.Call("c-1", http.MethodGet, "/v1/entries") if rec.Code != http.StatusBadGateway && rec.Code != http.StatusGatewayTimeout { t.Fatalf("status %d; a dead backend must surface as a gateway error", rec.Code) } if strings.Contains(rec.Body.String(), "127.0.0.1") { t.Error("the backend address leaked into the error response") } } // 7. Deterministic security enforcement independent of probabilistic decisions. func TestConformance07_DeterministicSecurity(t *testing.T) { h := New(t) // An unsigned descriptor is refused by the router regardless of anything // else about it. unsigned := contract.Revision{ SchemaVersion: "0.1", ID: "R-rogue", Interface: Interface, State: contract.RevisionStateStable, Contract: contract.RevisionContract{Type: contract.RevisionContractTypeOpenapi, Digest: contract.Digest("sha256:" + strings.Repeat("9", 64))}, Runtime: contract.RevisionRuntime{Upstream: h.AdapterURL("R-1")}, Intent: contract.RevisionIntent{Version: "IEI-1"}, Policy: contract.RevisionPolicy{ Compatibility: contract.RevisionPolicyCompatibilityAdditive, SecurityCheck: contract.RevisionPolicySecurityCheckPassed, }, } if err := h.Registry.PutRevision(unsigned); err == nil { t.Fatal("an unsigned descriptor was accepted by the router") } // A signed descriptor whose content was altered afterwards must also fail. sig, err := h.Signer.Sign(contract.RevisionDescriptorDocument{Revision: unsigned}) if err != nil { t.Fatal(err) } tampered := unsigned tampered.Runtime.Upstream = "http://attacker.invalid" tampered.Signature = &contract.RevisionSignature{ Algorithm: contract.RevisionSignatureAlgorithm(sig.Algorithm), KeyID: sig.KeyID, Value: sig.Value, } if err := h.Registry.PutRevision(tampered); err == nil { t.Fatal("a tampered descriptor was accepted by the router") } } // --------------------------------------------------------------------------- // ArchitectureBlueprint.md section 55 — architectural invariants // // Only the mechanically checkable subset is asserted. The rest are design // properties a test cannot settle, and pretending otherwise would be worse // than leaving them to review. // --------------------------------------------------------------------------- // Invariant 1: runtime behaviour remains deterministic. // Invariant 2: evolution can stop without stopping the API. func TestInvariant02_EvolutionCanStopWithoutStoppingTheAPI(t *testing.T) { h := New(t) // Kill the entire observation and control plane: telemetry delivery first, // then the evidence store itself. h.Drain() if err := h.Store.Close(); err != nil { t.Fatal(err) } // The data plane must keep serving from cached published configuration. for i := 0; i < 10; i++ { rec := h.Call("c-1", http.MethodGet, "/v1/entries") if rec.Code != http.StatusOK { t.Fatalf("request %d returned %d after the control plane died", i, rec.Code) } } } // Invariant 3: every published revision is identifiable. // Invariant 4: every revision has an explicit contract. // Invariant 5: every revision is governed by a specific intent version. func TestInvariant03to05_RevisionIdentityContractAndIntent(t *testing.T) { h := New(t) ctx := context.Background() for _, id := range []contract.RevisionID{"R-1", "R-2"} { d, err := h.Registry.Revision(id) if err != nil { t.Fatalf("%s is not registered: %v", id, err) } if d.ID == "" { t.Errorf("%s has no identity", id) } if d.Contract.Digest == "" { t.Errorf("%s has no contract digest", id) } if _, ok := h.Validator.Contract(d.Contract.Digest); !ok { t.Errorf("%s names a contract the gateway cannot enforce", id) } if d.Intent.Version == "" { t.Errorf("%s declares no governing intent", id) } if _, err := h.Intents.GoverningIntent(ctx, id); err != nil { t.Errorf("%s has no recorded intent binding: %v", id, err) } } } // Invariant 7: every experiment has guardrails and stop conditions. func TestInvariant07_ExperimentsAreBounded(t *testing.T) { h := New(t) ctx := context.Background() unbounded := contract.FluidExperiment{ ID: "E-unbounded", HypothesisRefs: []contract.HypothesisID{"H-1"}, ControlRevision: "R-1", CandidateRevisions: []contract.RevisionID{"R-2"}, Allocation: map[string]contract.UnitInterval{"control": 0.9, "candidate": 0.1}, Metrics: contract.FluidExperimentMetrics{Primary: []string{"m"}}, // No stop conditions. } if _, err := h.Experiments.Design(ctx, unbounded, Operator); err == nil { t.Error("an experiment with no stop condition was designed") } } // Invariant 8: every promotion is auditable. // Invariant 9: AI-generated artifacts are untrusted until verified. func TestInvariant09_GeneratedArtifactsAreUntrustedUntilVerified(t *testing.T) { h := New(t) // A candidate that has not been through the pipeline cannot be published: // Publish takes a Verified value and nothing else constructs one. if err := h.Pipeline.Publish(context.Background(), publish.Verified{}); err == nil { t.Fatal("an unverified value was published") } } // Invariant 10: security policy remains deterministic. func TestInvariant10_SecurityPolicyIsDeterministic(t *testing.T) { h := New(t) // The same descriptor must be judged identically every time. d, err := h.Registry.Revision("R-1") if err != nil { t.Fatal(err) } first := h.Registry.CheckRoutable("R-1", "agents") for i := 0; i < 50; i++ { if got := h.Registry.CheckRoutable("R-1", "agents"); (got == nil) != (first == nil) { t.Fatal("routability judgement varied between identical calls") } } _ = d } // Invariant 13: multiple revisions may coexist. func TestInvariant13_RevisionsCoexist(t *testing.T) { h := New(t) // Both revisions serve concurrently, distinguished only by resolution. mustStatus(t, h.CallPinned("c-1", "R-1", http.MethodGet, "/v1/entries"), http.StatusOK, "R-1") mustStatus(t, h.CallPinned("c-2", "R-2", http.MethodGet, "/v1/entries/latest"), http.StatusOK, "R-2 latest") // And the convenience resource exists only in R-2, which is what makes the // two revisions genuinely different contracts rather than a relabelling. mustStatus(t, h.CallPinned("c-3", "R-1", http.MethodGet, "/v1/entries/latest"), http.StatusBadRequest, "R-1 must not serve a path it does not declare") } // Invariant 14: failed experiments are normal and recoverable. func TestInvariant14_FailedExperimentsAreRecoverable(t *testing.T) { h := New(t) ctx := context.Background() loop := SeedHypothesis(t, h) exp := DesignExperiment(t, h, loop.Hypothesis) if _, _, err := h.Experiments.Start(ctx, exp, 2, "R-1", Operator); err != nil { t.Fatal(err) } // Stopping returns a policy with no rules; installing it puts every // consumer back on the known-good revision. _, stopPolicy, err := h.Experiments.Stop(ctx, exp, 3, "R-1", Operator, "guardrail breached") if err != nil { t.Fatal(err) } if len(stopPolicy.Rules) != 0 { t.Fatalf("the stop policy still carries %d rules", len(stopPolicy.Rules)) } if err := h.Registry.PutPolicy(stopPolicy); err != nil { t.Fatal(err) } for i := 0; i < 5; i++ { rec := h.Call("c-1", http.MethodGet, "/v1/entries") mustStatus(t, rec, http.StatusOK, "after rollback") if got := rec.Header().Get("X-FLUID-Revision"); got != "R-1" { t.Fatalf("traffic did not return to the control revision: %q", got) } } } // Invariant 16: adaptive cost is controlled independently of runtime // availability. Exhausting the adaptive budget must not affect serving. func TestInvariant16_AdaptiveBudgetDoesNotAffectRuntime(t *testing.T) { h := New(t) ctx := context.Background() // Saturate the experiment concurrency limit, the framework's current // expression of an adaptive budget. h.Experiments.SetMaxParallel(1) loop := SeedHypothesis(t, h) first := DesignExperiment(t, h, loop.Hypothesis) if _, _, err := h.Experiments.Start(ctx, first, 2, "R-1", Operator); err != nil { t.Fatal(err) } second := DesignExperimentNamed(t, h, loop.Hypothesis, "E-second") if _, _, err := h.Experiments.Start(ctx, second, 3, "R-1", Operator); err == nil { t.Fatal("the concurrency limit did not bind") } // With adaptation blocked, the runtime is unaffected. for i := 0; i < 5; i++ { mustStatus(t, h.Call("c-1", http.MethodGet, "/v1/entries"), http.StatusOK, "serving with the adaptive budget exhausted") } } // TestSignedDescriptorsRoundTrip guards the property the whole trust chain // rests on: what the pipeline signs is what the router verifies. func TestSignedDescriptorsRoundTrip(t *testing.T) { h := New(t) d, err := h.Registry.Revision("R-2") if err != nil { t.Fatal(err) } if d.Signature == nil { t.Fatal("a registered revision carries no signature") } // The registry already verified this on insert; re-verifying here confirms // the signature travelled with the descriptor rather than being dropped. verifier := signing.NewVerifier(map[string]ed25519.PublicKey{"conformance-key": h.PublicKey}) if err := verifier.Verify(contract.RevisionDescriptorDocument{Revision: d}, &signing.Signature{ Algorithm: string(d.Signature.Algorithm), KeyID: d.Signature.KeyID, Value: d.Signature.Value, }); err != nil { t.Fatalf("a registered descriptor does not verify: %v", err) } if d.Signature.Algorithm != contract.RevisionSignatureAlgorithmEd25519 { t.Errorf("unexpected signature algorithm %q", d.Signature.Algorithm) } } // TestTelemetryCarriesNoRawIdentity is the privacy boundary at the seam that // matters: whatever a consumer sends, the store must not keep it. func TestTelemetryCarriesNoRawIdentity(t *testing.T) { h := New(t) h.Call("bernd@example.com", http.MethodGet, "/v1/entries") h.Drain() for _, ev := range h.Telemetry() { if strings.Contains(ev.ConsumerRef, "@") { t.Fatalf("a raw consumer identity reached the store: %q", ev.ConsumerRef) } if ev.Redaction == nil { t.Error("an event does not record its redaction status") } } } var _ = evidence.EventFilter{} var _ = runtime.Request{}