Some checks failed
ci / build (push) Has been cancelled
Completes FLUID-WP-0007. The seven minimal-conformance requirements and the mechanically checkable architectural invariants are asserted as tests rather than claimed in a README, because a conformance claim nobody re-checks is one that quietly stops being true. Only the checkable subset of the invariants is asserted; pretending a test can settle the rest would be worse than leaving them to review. TestFirstVerticalSlice runs all eleven steps of Blueprint 50 with no human steps: two revisions, explicit routing, telemetry, a cohort dimension, detected pressure, a hypothesis, a candidate, a 90/10 experiment, fitness comparison, promotion, and a complete audit trail. Requests per completed task fall from 5.65 to 1.00 against a 1.20 target. A companion test runs the loop twice and requires the same verdict, since a loop whose conclusion depended on run order would be measuring the harness rather than the interface. The failure-containment matrix covers Blueprint 34 directly: the data plane keeps serving with the evidence store closed, with telemetry wedged against a sink that never returns, after a failed build, after an experiment rollback, and with the adaptive concurrency limit saturated. Fixes a real bug the suite exposed. Drain closed the emitter outright, so every request after the first flush emitted into a dead emitter and was silently lost -- the kind of fault that makes a later measurement quietly wrong rather than loudly broken. Emitter.Flush now waits for delivery without stopping it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014KmVxhJ35tCo7rE7UnLwWu Assistant: claude-code Assistant-Model: opus Assistant-Process: 1116572@bnt-lap001 Assistant-Session: 8ba9bb93-a72a-4883-b189-2499cce5c400
310 lines
9.7 KiB
Go
310 lines
9.7 KiB
Go
// Package suite is the FLUID conformance harness.
|
|
//
|
|
// It asserts the seven minimal-conformance requirements of
|
|
// FluidAPIStandards.md section 36 and the mechanically checkable subset of the
|
|
// sixteen architectural invariants in ArchitectureBlueprint.md section 55.
|
|
//
|
|
// Everything here runs with no external services and no human steps. A
|
|
// conformance claim that needed someone to run through it by hand would be a
|
|
// claim nobody re-checks after the first time.
|
|
package suite
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ed25519"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
_ "modernc.org/sqlite"
|
|
|
|
echo "github.com/tegwick/fluid-core/examples/echo-interface"
|
|
"github.com/tegwick/fluid-core/internal/contract"
|
|
"github.com/tegwick/fluid-core/internal/evidence"
|
|
"github.com/tegwick/fluid-core/internal/intent"
|
|
"github.com/tegwick/fluid-core/internal/observation"
|
|
"github.com/tegwick/fluid-core/internal/policy"
|
|
"github.com/tegwick/fluid-core/internal/publish"
|
|
"github.com/tegwick/fluid-core/internal/runtime"
|
|
"github.com/tegwick/fluid-core/internal/science"
|
|
"github.com/tegwick/fluid-core/internal/signing"
|
|
"github.com/tegwick/fluid-core/internal/validate"
|
|
)
|
|
|
|
// Interface is the fixture's interface identifier.
|
|
const Interface contract.InterfaceID = "echo-interface"
|
|
|
|
// IntentDocument is the fixture's governing intent.
|
|
const IntentDocument = `# Interface Evolution Intent — echo-interface
|
|
|
|
**Current operational authority mode:**
|
|
FLUID-4
|
|
|
|
## Mission
|
|
Serve entries to consumers, and learn from how they ask for them.
|
|
`
|
|
|
|
// Operator is the fixture's human actor.
|
|
var Operator = contract.Actor{Type: contract.ActorTypeHuman, ID: "conformance"}
|
|
|
|
// Harness is a fully wired FLUID deployment over the echo fixture.
|
|
type Harness struct {
|
|
T *testing.T
|
|
|
|
Store *evidence.SQLStore
|
|
Intents *intent.Store
|
|
Registry *runtime.Registry
|
|
Gateway *runtime.Gateway
|
|
Emitter *runtime.Emitter
|
|
Ingest *observation.Ingest
|
|
Pipeline *publish.Pipeline
|
|
Validator *validate.OpenAPIValidator
|
|
Signer *signing.Signer
|
|
PublicKey ed25519.PublicKey
|
|
|
|
Hypotheses *science.HypothesisStore
|
|
Experiments *science.ExperimentController
|
|
|
|
adapters map[contract.RevisionID]*httptest.Server
|
|
}
|
|
|
|
// New builds a harness with both fixture revisions published and R-1 stable.
|
|
func New(t *testing.T) *Harness {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
|
|
store, err := evidence.OpenSQLite(ctx, filepath.Join(t.TempDir(), "conformance.db"))
|
|
if err != nil {
|
|
t.Fatalf("open evidence store: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = store.Close() })
|
|
|
|
intents := intent.New(store, Interface)
|
|
if _, err := intents.Put(ctx, "IEI-1", IntentDocument); err != nil {
|
|
t.Fatalf("record intent: %v", err)
|
|
}
|
|
if err := intents.SetActive(ctx, "IEI-1"); err != nil {
|
|
t.Fatalf("activate intent: %v", err)
|
|
}
|
|
|
|
signer, pub, err := signing.GenerateKey("conformance-key")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
limits := policy.DefaultLimits()
|
|
// The fixture's intent grants FLUID-4, and the loop under test promotes a
|
|
// contract adaptation, so the fixture permits that class explicitly.
|
|
limits.AllowedAdaptationClasses = append(limits.AllowedAdaptationClasses,
|
|
contract.AdaptationClassContract)
|
|
limits.RequiredMode = intent.ModeExperimental
|
|
gate := policy.NewGate(limits)
|
|
|
|
pipeline, err := publish.New(publish.Options{
|
|
Gate: gate, Signer: signer, Store: store, Intents: intents,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
salt := []byte("conformance-salt-of-sufficient-length")
|
|
ingest, err := observation.NewIngest(store, Interface, observation.DefaultRedactionPolicy(salt))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
h := &Harness{
|
|
T: t, Store: store, Intents: intents, Pipeline: pipeline,
|
|
Validator: validate.NewOpenAPIValidator(), Signer: signer, PublicKey: pub,
|
|
Ingest: ingest,
|
|
adapters: map[contract.RevisionID]*httptest.Server{},
|
|
}
|
|
|
|
// A verifying registry: the router must accept only signed descriptors.
|
|
h.Registry = runtime.NewVerifiedRegistry(Interface,
|
|
signing.NewVerifier(map[string]ed25519.PublicKey{"conformance-key": pub}))
|
|
|
|
h.adapters["R-1"] = httptest.NewServer(echo.NewR1())
|
|
h.adapters["R-2"] = httptest.NewServer(echo.NewR2())
|
|
t.Cleanup(func() {
|
|
for _, s := range h.adapters {
|
|
s.Close()
|
|
}
|
|
})
|
|
|
|
h.Emitter = runtime.NewEmitter(ingest, runtime.EmitterOptions{Buffer: 8192, Workers: 2})
|
|
|
|
gw, err := runtime.NewGateway(runtime.GatewayOptions{
|
|
Interface: Interface,
|
|
Registry: h.Registry,
|
|
Resolver: runtime.NewResolver(h.Registry, true),
|
|
Connector: runtime.NewConnector(),
|
|
Emitter: h.Emitter,
|
|
Cohorts: observation.NewCohortEngine("agents", observation.DefaultRedactionPolicy(salt)),
|
|
Response: runtime.ResponsePolicy{FeedbackPath: "/v1/feedback"},
|
|
Validator: h.Validator,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
h.Gateway = gw
|
|
|
|
h.Hypotheses = science.NewHypothesisStore(store, Interface)
|
|
h.Experiments = science.NewExperimentController(store, h.Hypotheses, Interface)
|
|
|
|
h.PublishRevision("R-1", "examples/echo-interface/r1.openapi.yaml", nil)
|
|
h.PublishRevision("R-2", "examples/echo-interface/r2.openapi.yaml",
|
|
[]contract.AdaptationClass{contract.AdaptationClassContract})
|
|
h.InstallPolicy(1, "R-1", nil)
|
|
|
|
return h
|
|
}
|
|
|
|
// PublishRevision takes a revision through the full pipeline and registers it.
|
|
func (h *Harness) PublishRevision(id contract.RevisionID, contractPath string, classes []contract.AdaptationClass) contract.Revision {
|
|
h.T.Helper()
|
|
ctx := context.Background()
|
|
|
|
raw := readRepoFile(h.T, contractPath)
|
|
digest := signing.Digest(raw)
|
|
|
|
if err := h.Validator.Register(digest, raw); err != nil {
|
|
h.T.Fatalf("register contract for %s: %v", id, err)
|
|
}
|
|
|
|
pc := contract.RevisionPolicyPolicyCheckPassed
|
|
descriptor := contract.Revision{
|
|
SchemaVersion: "0.1",
|
|
ID: id,
|
|
Interface: Interface,
|
|
State: contract.RevisionStateStable,
|
|
Contract: contract.RevisionContract{
|
|
Type: contract.RevisionContractTypeOpenapi,
|
|
Digest: digest,
|
|
Source: contractPath,
|
|
},
|
|
Runtime: contract.RevisionRuntime{Upstream: h.adapters[id].URL},
|
|
Intent: contract.RevisionIntent{Version: "IEI-1"},
|
|
Policy: contract.RevisionPolicy{
|
|
Compatibility: contract.RevisionPolicyCompatibilityAdditive,
|
|
SecurityCheck: contract.RevisionPolicySecurityCheckPassed,
|
|
PolicyCheck: &pc,
|
|
},
|
|
}
|
|
|
|
if classes == nil {
|
|
classes = []contract.AdaptationClass{contract.AdaptationClassPresentation}
|
|
}
|
|
|
|
verified, report, err := h.Pipeline.Run(ctx, publish.NewCandidate(descriptor, Operator),
|
|
publish.PromotionRequest{
|
|
AdaptationClasses: classes,
|
|
ComplexityDelta: 0.2,
|
|
RequestedTrafficShare: 0.2,
|
|
Approved: true,
|
|
ApprovedBy: &Operator,
|
|
})
|
|
if err != nil {
|
|
h.T.Fatalf("publish %s: %v (%+v)", id, err, report.Stages)
|
|
}
|
|
if err := h.Pipeline.Publish(ctx, verified); err != nil {
|
|
h.T.Fatalf("record %s: %v", id, err)
|
|
}
|
|
if err := h.Registry.PutRevision(verified.Descriptor()); err != nil {
|
|
h.T.Fatalf("register %s: %v", id, err)
|
|
}
|
|
return verified.Descriptor()
|
|
}
|
|
|
|
// InstallPolicy loads a routing policy into the registry.
|
|
func (h *Harness) InstallPolicy(generation int64, defaultRevision contract.RevisionID, rules []contract.RoutingPolicyRulesItem) {
|
|
h.T.Helper()
|
|
if rules == nil {
|
|
rules = []contract.RoutingPolicyRulesItem{}
|
|
}
|
|
if err := h.Registry.PutPolicy(contract.RoutingPolicy{
|
|
SchemaVersion: "0.1",
|
|
Interface: Interface,
|
|
Generation: generation,
|
|
DefaultRevision: defaultRevision,
|
|
Rules: rules,
|
|
}); err != nil {
|
|
h.T.Fatalf("install policy generation %d: %v", generation, err)
|
|
}
|
|
}
|
|
|
|
// Call issues a request through the gateway as a named consumer.
|
|
func (h *Harness) Call(consumer, method, target string) *httptest.ResponseRecorder {
|
|
h.T.Helper()
|
|
req := httptest.NewRequest(method, target, nil)
|
|
req.Header.Set("X-FLUID-Consumer", consumer)
|
|
rec := httptest.NewRecorder()
|
|
h.Gateway.ServeHTTP(rec, req)
|
|
return rec
|
|
}
|
|
|
|
// CallPinned issues a request pinned to a specific revision.
|
|
func (h *Harness) CallPinned(consumer string, rev contract.RevisionID, method, target string) *httptest.ResponseRecorder {
|
|
h.T.Helper()
|
|
req := httptest.NewRequest(method, target, nil)
|
|
req.Header.Set("X-FLUID-Consumer", consumer)
|
|
req.Header.Set("X-FLUID-Revision", string(rev))
|
|
rec := httptest.NewRecorder()
|
|
h.Gateway.ServeHTTP(rec, req)
|
|
return rec
|
|
}
|
|
|
|
// Drain flushes buffered telemetry so assertions see every event.
|
|
//
|
|
// It flushes rather than closes: the harness keeps serving after a drain, and
|
|
// closing here would mean every later request emitted into a dead emitter.
|
|
func (h *Harness) Drain() {
|
|
if !h.Emitter.Flush(5 * time.Second) {
|
|
h.T.Fatal("telemetry did not reach the store within 5s")
|
|
}
|
|
}
|
|
|
|
// Telemetry returns everything recorded for the interface.
|
|
func (h *Harness) Telemetry() []contract.FluidTelemetry {
|
|
h.T.Helper()
|
|
rows, err := h.Store.Telemetry(context.Background(),
|
|
evidence.TelemetryFilter{InterfaceID: Interface})
|
|
if err != nil {
|
|
h.T.Fatal(err)
|
|
}
|
|
return rows
|
|
}
|
|
|
|
// Events returns audit events for an entity.
|
|
func (h *Harness) Events(entityID string) []contract.FluidEvent {
|
|
h.T.Helper()
|
|
events, err := h.Store.Events(context.Background(), evidence.EventFilter{EntityID: entityID})
|
|
if err != nil {
|
|
h.T.Fatal(err)
|
|
}
|
|
return events
|
|
}
|
|
|
|
// AdapterURL returns a fixture adapter's address.
|
|
func (h *Harness) AdapterURL(rev contract.RevisionID) string {
|
|
return h.adapters[rev].URL
|
|
}
|
|
|
|
// StopAdapter kills a fixture adapter, for failure-containment tests.
|
|
func (h *Harness) StopAdapter(rev contract.RevisionID) {
|
|
h.adapters[rev].Close()
|
|
}
|
|
|
|
// mustStatus fails unless the response carries the expected status.
|
|
func mustStatus(t *testing.T, rec *httptest.ResponseRecorder, want int, context string) {
|
|
t.Helper()
|
|
if rec.Code != want {
|
|
t.Fatalf("%s: status %d, want %d; body %s", context, rec.Code, want, rec.Body.String())
|
|
}
|
|
}
|
|
|
|
var _ = fmt.Sprintf
|
|
var _ = http.MethodGet
|