From fbbf56df7abe1600e79f6e639612c64d7ca76814 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 4 Sep 2026 01:55:47 +0200 Subject: [PATCH] Add FLUID wire contract schemas Nine JSON Schemas covering the five record types from the schema spec, plus backend requirements, feedback, the revision descriptor, routing policy and the telemetry envelope. These are the integration boundary: readable without any fluid-core code, so adapters may be written in any language. Co-Authored-By: Claude Opus 5 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 --- schemas/backend-requirement.schema.json | 83 +++++++++ schemas/common.schema.json | 161 ++++++++++++++++ schemas/event.schema.json | 35 ++++ schemas/experiment.schema.json | 99 ++++++++++ schemas/feedback.schema.json | 40 ++++ schemas/hypothesis.schema.json | 228 +++++++++++++++++++++++ schemas/pressure.schema.json | 53 ++++++ schemas/revision-descriptor.schema.json | 124 +++++++++++++ schemas/revision.schema.json | 235 ++++++++++++++++++++++++ schemas/routing-policy.schema.json | 71 +++++++ schemas/telemetry-envelope.schema.json | 113 ++++++++++++ 11 files changed, 1242 insertions(+) create mode 100644 schemas/backend-requirement.schema.json create mode 100644 schemas/common.schema.json create mode 100644 schemas/event.schema.json create mode 100644 schemas/experiment.schema.json create mode 100644 schemas/feedback.schema.json create mode 100644 schemas/hypothesis.schema.json create mode 100644 schemas/pressure.schema.json create mode 100644 schemas/revision-descriptor.schema.json create mode 100644 schemas/revision.schema.json create mode 100644 schemas/routing-policy.schema.json create mode 100644 schemas/telemetry-envelope.schema.json diff --git a/schemas/backend-requirement.schema.json b/schemas/backend-requirement.schema.json new file mode 100644 index 0000000..697b17e --- /dev/null +++ b/schemas/backend-requirement.schema.json @@ -0,0 +1,83 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://fluid.dev/schemas/backend-requirement.schema.json", + "title": "FLUID backend capability requirement", + "description": "Capability the interface needs but has no authority to build. FLUID escalates rather than crossing the boundary (FluidAPIStandards.md section 30).", + "type": "object", + "properties": { + "fluid_backend_requirement": { + "type": "object", + "properties": { + "schema_version": { "$ref": "common.schema.json#/$defs/schemaVersion" }, + "id": { "$ref": "common.schema.json#/$defs/backendRequirementId" }, + "originating_interface": { "$ref": "common.schema.json#/$defs/interfaceId" }, + "originating_revision": { "oneOf": [{ "$ref": "common.schema.json#/$defs/revisionId" }, { "type": "null" }] }, + "originating_hypothesis": { "oneOf": [{ "$ref": "common.schema.json#/$defs/hypothesisId" }, { "type": "null" }] }, + "backend_service": { "type": "string", "minLength": 1 }, + + "capability": { + "type": "object", + "properties": { + "title": { "type": "string", "minLength": 1 }, + "description": { "type": "string", "minLength": 1 } + }, + "required": ["title", "description"], + "additionalProperties": false + }, + + "semantics": { + "type": "object", + "properties": { + "consistency": { "type": "string" }, + "required_fields": { "type": "array", "items": { "type": "string" } } + }, + "additionalProperties": true + }, + + "quality": { + "type": "object", + "properties": { + "p95_latency_ms": { "type": "number", "minimum": 0 }, + "availability": { "$ref": "common.schema.json#/$defs/unitInterval" } + }, + "additionalProperties": true + }, + + "security": { + "type": "object", + "properties": { + "authorization_scope": { "type": "string" }, + "tenant_isolation": { "type": "string" } + }, + "additionalProperties": true + }, + + "expected_usage": { + "type": "object", + "properties": { + "requests_per_day": { "type": "integer", "minimum": 0 } + }, + "additionalProperties": true + }, + + "urgency": { "enum": ["LOW", "MEDIUM", "HIGH"] }, + + "disposition": { + "type": "object", + "description": "The backend decides. Repeated OUT_OF_SCOPE feeds boundary learning (ArchitectureBlueprint.md section 22).", + "properties": { + "state": { "enum": ["OPEN", "ACCEPTED", "PLANNED", "AVAILABLE", "PARTIALLY_AVAILABLE", "OUT_OF_SCOPE", "REJECTED", "SUPERSEDED"] }, + "reason": { "type": ["string", "null"] }, + "target_ref": { "type": ["string", "null"] } + }, + "required": ["state"], + "additionalProperties": false + } + }, + "required": ["schema_version", "id", "originating_interface", "backend_service", "capability", "disposition"], + "additionalProperties": false + } + }, + "required": ["fluid_backend_requirement"], + "additionalProperties": false +} diff --git a/schemas/common.schema.json b/schemas/common.schema.json new file mode 100644 index 0000000..06e67fe --- /dev/null +++ b/schemas/common.schema.json @@ -0,0 +1,161 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://fluid.dev/schemas/common.schema.json", + "title": "FLUID common definitions", + "description": "Shared identifier, reference and measurement types. Identifier prefixes follow FluidHypothesisRevisionSchema.md section 16.", + "$defs": { + "schemaVersion": { + "type": "string", + "const": "0.1" + }, + "interfaceId": { + "type": "string", + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "description": "Stable identifier of the owning interface." + }, + "hypothesisId": { "type": "string", "pattern": "^H-[0-9A-Za-z_-]+$" }, + "revisionId": { "type": "string", "pattern": "^R-[0-9A-Za-z_-]+$" }, + "experimentId": { "type": "string", "pattern": "^E-[0-9A-Za-z_-]+$" }, + "pressureId": { "type": "string", "pattern": "^P-[0-9A-Za-z_-]+$" }, + "backendRequirementId": { "type": "string", "pattern": "^BR-[0-9A-Za-z_-]+$" }, + "decisionId": { "type": "string", "pattern": "^D-[0-9A-Za-z_-]+$" }, + "eventId": { "type": "string", "pattern": "^EV-[0-9A-Za-z_-]+$" }, + "feedbackId": { "type": "string", "pattern": "^F-[0-9A-Za-z_-]+$" }, + "cohortId": { "type": "string", "minLength": 1 }, + + "timestamp": { "type": "string", "format": "date-time" }, + + "evidenceRef": { + "type": "string", + "minLength": 1, + "description": "Opaque reference into the evidence store, conventionally ':' such as 'telemetry:invoice-pattern-2026w36'." + }, + "artifactRef": { + "type": "string", + "minLength": 1, + "description": "Opaque reference into the artifact store." + }, + "digest": { + "type": "string", + "pattern": "^sha256:[a-f0-9]{64}$", + "description": "Content address of an artifact." + }, + + "actor": { + "type": "object", + "description": "Who or what performed an action. FLUID distinguishes these because trust levels differ (ArchitectureBlueprint.md section 47).", + "properties": { + "type": { "enum": ["human", "daimon", "policy", "system", "consumer"] }, + "id": { "type": "string", "minLength": 1 }, + "model_or_agent": { "type": "string" } + }, + "required": ["type", "id"], + "additionalProperties": false + }, + + "observationWindow": { + "type": "object", + "properties": { + "start": { "$ref": "common.schema.json#/$defs/timestamp" }, + "end": { + "oneOf": [ + { "$ref": "common.schema.json#/$defs/timestamp" }, + { "type": "null" } + ], + "description": "Null while the window is still open." + } + }, + "required": ["start"], + "additionalProperties": false + }, + + "unitInterval": { "type": "number", "minimum": 0, "maximum": 1 }, + + "pressureClass": { + "enum": [ + "natural_usage", + "successful_but_inefficient_usage", + "recoverable_misunderstanding", + "repeated_expectation_mismatch", + "poor_discoverability", + "missing_interface_capability", + "missing_backend_capability", + "out_of_scope_demand", + "prohibited_demand", + "implementation_failure" + ], + "description": "FluidAPIStandards.md section 13." + }, + + "adaptationClass": { + "enum": ["presentation", "contract", "composition", "implementation", "requirement_escalation"], + "description": "FluidAPIStandards.md section 16." + }, + + "guardrail": { + "type": "object", + "description": "A constraint that must not regress beyond threshold. Guardrails are evaluated deterministically.", + "properties": { + "metric": { "type": "string", "minLength": 1 }, + "operator": { "enum": ["<", "<=", "==", "!=", ">=", ">"] }, + "threshold": { "type": ["number", "boolean", "string"] } + }, + "required": ["metric", "operator", "threshold"], + "additionalProperties": false + }, + + "expectedOutcome": { + "type": "object", + "properties": { + "metric": { "type": "string", "minLength": 1 }, + "cohort": { "type": "string" }, + "baseline": { "type": "number" }, + "target": { "type": "number" }, + "direction": { "enum": ["lower", "higher", "unchanged"] } + }, + "required": ["metric", "target", "direction"], + "additionalProperties": false + }, + + "complexityDelta": { + "type": "object", + "description": "Standard dimensions, implementation-defined scoring (FluidAPIStandards.md section 22).", + "properties": { + "surface_area": { "type": "number" }, + "concept_count": { "type": "number" }, + "operation_count": { "type": "number" }, + "parameter_dimensionality": { "type": "number" }, + "semantic_overlap": { "type": "number" }, + "alternative_path_count": { "type": "number" }, + "dependency_count": { "type": "number" }, + "backend_composition_count": { "type": "number" }, + "exception_count": { "type": "number" }, + "active_revision_count": { "type": "number" }, + "cognitive_load": { "type": "number" } + }, + "additionalProperties": false + }, + + "fitnessDimensions": { + "type": "object", + "description": "FluidAPIStandards.md section 20. No universal scalar is defined on purpose.", + "properties": { + "client_utility": { "type": "number" }, + "correctness": { "type": "number" }, + "reliability": { "type": "number" }, + "availability": { "type": "number" }, + "performance": { "type": "number" }, + "discoverability": { "type": "number" }, + "simplicity": { "type": "number" }, + "compatibility": { "type": "number" }, + "security": { "type": "number" }, + "maintainability": { "type": "number" }, + "operational_cost": { "type": "number" }, + "implementation_cost": { "type": "number" }, + "resource_consumption": { "type": "number" } + }, + "additionalProperties": false + } + } +} diff --git a/schemas/event.schema.json b/schemas/event.schema.json new file mode 100644 index 0000000..cf2a3a3 --- /dev/null +++ b/schemas/event.schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://fluid.dev/schemas/event.schema.json", + "title": "FLUID audit event", + "description": "Append-only record of a material lifecycle transition. FLUID evolution is itself a system behavior that must remain reconstructable (FluidHypothesisRevisionSchema.md section 15).", + "type": "object", + "properties": { + "fluid_event": { + "type": "object", + "properties": { + "schema_version": { "$ref": "common.schema.json#/$defs/schemaVersion" }, + "id": { "$ref": "common.schema.json#/$defs/eventId" }, + "occurred_at": { "$ref": "common.schema.json#/$defs/timestamp" }, + "entity_type": { "enum": ["pressure", "hypothesis", "revision", "experiment", "backend_requirement", "intent", "decision", "routing_policy"] }, + "entity_id": { "type": "string", "minLength": 1 }, + "event_type": { "type": "string", "minLength": 1 }, + "actor": { "$ref": "common.schema.json#/$defs/actor" }, + "inputs": { + "type": "array", + "items": { "type": "string" }, + "description": "Identifiers of the records this transition drew on." + }, + "reason": { "type": "string" }, + "evidence_refs": { + "type": "array", + "items": { "$ref": "common.schema.json#/$defs/evidenceRef" } + } + }, + "required": ["schema_version", "id", "occurred_at", "entity_type", "entity_id", "event_type", "actor"], + "additionalProperties": false + } + }, + "required": ["fluid_event"], + "additionalProperties": false +} diff --git a/schemas/experiment.schema.json b/schemas/experiment.schema.json new file mode 100644 index 0000000..5e673d8 --- /dev/null +++ b/schemas/experiment.schema.json @@ -0,0 +1,99 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://fluid.dev/schemas/experiment.schema.json", + "title": "FLUID experiment record", + "description": "Connects hypotheses to revisions under bounded conditions. Experiments must be interruptible (ArchitectureBlueprint.md section 16).", + "type": "object", + "properties": { + "fluid_experiment": { + "type": "object", + "properties": { + "schema_version": { "$ref": "common.schema.json#/$defs/schemaVersion" }, + "id": { "$ref": "common.schema.json#/$defs/experimentId" }, + "interface_id": { "$ref": "common.schema.json#/$defs/interfaceId" }, + "hypothesis_refs": { + "type": "array", + "items": { "$ref": "common.schema.json#/$defs/hypothesisId" }, + "minItems": 1, + "description": "An experiment without a hypothesis measures nothing in particular." + }, + "mechanism": { + "enum": ["sandbox", "shadow", "synthetic", "replay", "canary", "opt_in", "cohort", "tenant", "percentage"] + }, + "control_revision": { "$ref": "common.schema.json#/$defs/revisionId" }, + "candidate_revisions": { + "type": "array", + "items": { "$ref": "common.schema.json#/$defs/revisionId" }, + "minItems": 1 + }, + "cohorts": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/cohortId" } }, + "allocation": { + "type": "object", + "description": "Shares by revision id or by the reserved key 'control'. Allocation is deterministic and enacted by the router, never by this record.", + "additionalProperties": { "$ref": "common.schema.json#/$defs/unitInterval" }, + "minProperties": 2 + }, + "start_at": { "$ref": "common.schema.json#/$defs/timestamp" }, + "planned_end_at": { "oneOf": [{ "$ref": "common.schema.json#/$defs/timestamp" }, { "type": "null" }] }, + "max_duration_hours": { "type": "number", "exclusiveMinimum": 0 }, + + "metrics": { + "type": "object", + "description": "Primary metrics and guardrails are kept distinct so a scalar can never hide a regression (ArchitectureBlueprint.md section 48.5).", + "properties": { + "primary": { "type": "array", "items": { "type": "string" }, "minItems": 1 }, + "guardrails": { "type": "array", "items": { "type": "string" } }, + "secondary": { "type": "array", "items": { "type": "string" } }, + "learning": { "type": "array", "items": { "type": "string" } } + }, + "required": ["primary"], + "additionalProperties": false + }, + + "start_conditions": { "type": "array", "items": { "type": "string" } }, + "stop_conditions": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "description": "Every experiment has a stop condition (ArchitectureBlueprint.md section 55, invariant 7)." + }, + + "amendments": { + "type": "array", + "description": "Success criteria may not be changed after results are visible without recording the amendment (ArchitectureBlueprint.md section 18).", + "items": { + "type": "object", + "properties": { + "at": { "$ref": "common.schema.json#/$defs/timestamp" }, + "actor": { "$ref": "common.schema.json#/$defs/actor" }, + "change": { "type": "string", "minLength": 1 }, + "reason": { "type": "string", "minLength": 1 } + }, + "required": ["at", "actor", "change", "reason"], + "additionalProperties": false + } + }, + + "result": { + "type": "object", + "properties": { + "state": { "enum": ["PLANNED", "RUNNING", "STOPPED", "COMPLETED", "ABANDONED"] }, + "preferred_revision": { "oneOf": [{ "$ref": "common.schema.json#/$defs/revisionId" }, { "type": "null" }] }, + "stopped_reason": { "type": ["string", "null"] }, + "evidence_refs": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/evidenceRef" } } + }, + "required": ["state"], + "additionalProperties": false + } + }, + "required": [ + "schema_version", "id", "interface_id", "hypothesis_refs", + "control_revision", "candidate_revisions", "allocation", + "metrics", "stop_conditions", "result" + ], + "additionalProperties": false + } + }, + "required": ["fluid_experiment"], + "additionalProperties": false +} diff --git a/schemas/feedback.schema.json b/schemas/feedback.schema.json new file mode 100644 index 0000000..8e7b31d --- /dev/null +++ b/schemas/feedback.schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://fluid.dev/schemas/feedback.schema.json", + "title": "FLUID explicit consumer feedback", + "description": "Structured feedback from a consumer. Treated as evidence and never as authority to change anything (FluidAPIStandards.md section 15).", + "type": "object", + "properties": { + "fluid_feedback": { + "type": "object", + "properties": { + "schema_version": { "$ref": "common.schema.json#/$defs/schemaVersion" }, + "id": { "$ref": "common.schema.json#/$defs/feedbackId" }, + "interface_id": { "$ref": "common.schema.json#/$defs/interfaceId" }, + "received_at": { "$ref": "common.schema.json#/$defs/timestamp" }, + "cohort": { "$ref": "common.schema.json#/$defs/cohortId" }, + "revision": { "$ref": "common.schema.json#/$defs/revisionId" }, + "goal": { "type": "string" }, + "attempt": { "type": "string" }, + "attempted_operations": { "type": "array", "items": { "type": "string" } }, + "outcome": { "type": "string" }, + "missing_capability": { "type": "string" }, + "workaround": { "type": ["string", "array"], "items": { "type": "string" } }, + "impact": { + "type": "object", + "properties": { + "description": { "type": "string" }, + "calls": { "type": "integer", "minimum": 0 }, + "latency_ms": { "type": "number", "minimum": 0 } + }, + "additionalProperties": false + }, + "confidence": { "$ref": "common.schema.json#/$defs/unitInterval" } + }, + "required": ["schema_version", "id", "interface_id", "received_at", "goal"], + "additionalProperties": false + } + }, + "required": ["fluid_feedback"], + "additionalProperties": false +} diff --git a/schemas/hypothesis.schema.json b/schemas/hypothesis.schema.json new file mode 100644 index 0000000..e29181e --- /dev/null +++ b/schemas/hypothesis.schema.json @@ -0,0 +1,228 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://fluid.dev/schemas/hypothesis.schema.json", + "title": "FLUID hypothesis record", + "description": "Why an interface change should be explored. Observation, explanation, prediction, intervention and result stay separate on purpose (FluidHypothesisRevisionSchema.md section 18).", + "type": "object", + "properties": { + "fluid_hypothesis": { + "type": "object", + "properties": { + "schema_version": { "$ref": "common.schema.json#/$defs/schemaVersion" }, + "id": { "$ref": "common.schema.json#/$defs/hypothesisId" }, + "interface_id": { "$ref": "common.schema.json#/$defs/interfaceId" }, + "state": { + "enum": ["DRAFT", "READY", "PRIORITIZED", "DESIGNING", "EXPERIMENTING", "EVALUATING", "ACCEPTED", "REJECTED", "SUPERSEDED", "DEFERRED"] + }, + "created_at": { "$ref": "common.schema.json#/$defs/timestamp" }, + "created_by": { "$ref": "common.schema.json#/$defs/actor" }, + "title": { "type": "string", "minLength": 1 }, + + "observation": { + "type": "object", + "description": "What was seen. Not what it means.", + "properties": { + "summary": { "type": "string", "minLength": 1 }, + "evidence_refs": { + "type": "array", + "items": { "$ref": "common.schema.json#/$defs/evidenceRef" }, + "minItems": 1 + }, + "affected_cohorts": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/cohortId" } }, + "observation_window": { "$ref": "common.schema.json#/$defs/observationWindow" } + }, + "required": ["summary", "evidence_refs"], + "additionalProperties": false + }, + + "pressure": { + "type": "object", + "properties": { + "classes": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/pressureClass" }, "minItems": 1 }, + "pressure_refs": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/pressureId" } }, + "severity": { "$ref": "common.schema.json#/$defs/unitInterval" }, + "confidence": { "$ref": "common.schema.json#/$defs/unitInterval" } + }, + "required": ["classes"], + "additionalProperties": false + }, + + "explanation": { + "type": "object", + "description": "What we think explains the observation.", + "properties": { + "claim": { "type": "string", "minLength": 1 }, + "reach": { + "type": "object", + "description": "Explanatory reach is a prioritization signal, never a correctness claim (FluidAPIStandards.md section 18).", + "properties": { + "score": { "type": "number" }, + "explains": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/pressureId" } }, + "notes": { "type": "string" } + }, + "additionalProperties": false + } + }, + "required": ["claim"], + "additionalProperties": false + }, + + "proposed_adaptation": { + "type": "object", + "description": "What we propose to change.", + "properties": { + "class": { "$ref": "common.schema.json#/$defs/adaptationClass" }, + "summary": { "type": "string", "minLength": 1 }, + "candidate_contract": { + "type": "object", + "description": "Shape is protocol-specific and deliberately unconstrained.", + "additionalProperties": true + }, + "implementation_scope": { "enum": ["interface_only", "interface_and_backend", "documentation_only"] } + }, + "required": ["class", "summary"], + "additionalProperties": false + }, + + "backend_requirements": { + "type": "object", + "properties": { + "required": { "type": "boolean" }, + "requirement_refs": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/backendRequirementId" } } + }, + "required": ["required"], + "additionalProperties": false + }, + + "expected_outcomes": { + "type": "array", + "items": { "$ref": "common.schema.json#/$defs/expectedOutcome" }, + "minItems": 1, + "description": "A hypothesis must be falsifiable (FluidAPIStandards.md section 17)." + }, + "guardrails": { + "type": "array", + "items": { "$ref": "common.schema.json#/$defs/guardrail" } + }, + + "fitness_dimensions": { + "type": "object", + "properties": { + "expected": { "$ref": "common.schema.json#/$defs/fitnessDimensions" } + }, + "additionalProperties": false + }, + + "complexity": { + "type": "object", + "properties": { + "expected_delta": { "$ref": "common.schema.json#/$defs/complexityDelta" }, + "score": { "type": "number" } + }, + "required": ["expected_delta"], + "additionalProperties": false + }, + + "risk": { + "type": "object", + "properties": { + "level": { "enum": ["LOW", "MEDIUM", "HIGH", "CRITICAL"] }, + "reasons": { "type": "array", "items": { "type": "string" } } + }, + "required": ["level"], + "additionalProperties": false + }, + + "economics": { + "type": "object", + "description": "A hypothesis may be valuable but not yet worth exploring (ArchitectureBlueprint.md section 29).", + "properties": { + "estimated_experiment_cost": { "type": "number", "minimum": 0 }, + "estimated_implementation_cost": { "type": "number", "minimum": 0 }, + "currency": { "type": "string" }, + "expected_value_class": { "enum": ["LOW", "MEDIUM", "HIGH"] } + }, + "additionalProperties": false + }, + + "learning_value": { + "type": "object", + "properties": { + "score": { "type": "number" }, + "notes": { "type": "string" } + }, + "additionalProperties": false + }, + + "priority": { + "type": "object", + "properties": { + "score": { "type": "number" }, + "decided_by": { "type": "string" } + }, + "additionalProperties": false + }, + + "competition": { + "type": "object", + "description": "Competing hypotheses are legitimate (FluidAPIStandards.md section 19).", + "properties": { + "group_id": { "type": "string", "minLength": 1 }, + "alternatives": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/hypothesisId" } } + }, + "required": ["group_id"], + "additionalProperties": false + }, + + "success_criteria": { + "type": "object", + "properties": { "expression": { "type": "string", "minLength": 1 } }, + "required": ["expression"], + "additionalProperties": false + }, + "failure_criteria": { + "type": "object", + "properties": { "expression": { "type": "string", "minLength": 1 } }, + "required": ["expression"], + "additionalProperties": false + }, + + "candidate_revision_refs": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/revisionId" } }, + "experiment_refs": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/experimentId" } }, + + "outcome": { + "type": "object", + "description": "What happened afterwards. Null until evaluation completes.", + "properties": { + "status": { + "oneOf": [ + { "enum": ["CONFIRMED", "REFUTED", "INCONCLUSIVE"] }, + { "type": "null" } + ] + }, + "summary": { "type": ["string", "null"] }, + "evidence_refs": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/evidenceRef" } } + }, + "additionalProperties": false + }, + + "audit": { + "type": "object", + "properties": { + "decision_refs": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/decisionId" } }, + "immutable_event_refs": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/eventId" } } + }, + "additionalProperties": false + } + }, + "required": [ + "schema_version", "id", "interface_id", "state", "title", + "observation", "pressure", "explanation", "proposed_adaptation", + "expected_outcomes", "guardrails", "complexity", "risk", "success_criteria" + ], + "additionalProperties": false + } + }, + "required": ["fluid_hypothesis"], + "additionalProperties": false +} diff --git a/schemas/pressure.schema.json b/schemas/pressure.schema.json new file mode 100644 index 0000000..5d5a20f --- /dev/null +++ b/schemas/pressure.schema.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://fluid.dev/schemas/pressure.schema.json", + "title": "FLUID pressure record", + "description": "Evidence that the interface differs materially from consumer needs. Pressure is evidence, not truth (FluidAPIStandards.md section 13).", + "type": "object", + "properties": { + "fluid_pressure": { + "type": "object", + "properties": { + "schema_version": { "$ref": "common.schema.json#/$defs/schemaVersion" }, + "id": { "$ref": "common.schema.json#/$defs/pressureId" }, + "interface_id": { "$ref": "common.schema.json#/$defs/interfaceId" }, + "class": { "$ref": "common.schema.json#/$defs/pressureClass" }, + "first_seen": { "$ref": "common.schema.json#/$defs/timestamp" }, + "last_seen": { "$ref": "common.schema.json#/$defs/timestamp" }, + "affected_cohorts": { + "type": "array", + "items": { "$ref": "common.schema.json#/$defs/cohortId" } + }, + "frequency": { + "type": "object", + "properties": { + "observations": { "type": "integer", "minimum": 0 }, + "independent_consumers": { "type": "integer", "minimum": 0 } + }, + "additionalProperties": false + }, + "severity": { "$ref": "common.schema.json#/$defs/unitInterval" }, + "confidence": { "$ref": "common.schema.json#/$defs/unitInterval" }, + "summary": { "type": "string", "minLength": 1 }, + "evidence_refs": { + "type": "array", + "items": { "$ref": "common.schema.json#/$defs/evidenceRef" }, + "minItems": 1, + "description": "A pressure record without evidence references is not auditable and must be rejected." + }, + "status": { + "enum": ["OPEN", "ANALYZING", "EXPLAINED", "ADDRESSED", "DISMISSED", "OUT_OF_SCOPE"], + "description": "ArchitectureBlueprint.md section 9. Pressure may remain unresolved on purpose." + }, + "linked_hypotheses": { + "type": "array", + "items": { "$ref": "common.schema.json#/$defs/hypothesisId" } + } + }, + "required": ["schema_version", "id", "interface_id", "class", "first_seen", "last_seen", "summary", "evidence_refs", "status"], + "additionalProperties": false + } + }, + "required": ["fluid_pressure"], + "additionalProperties": false +} diff --git a/schemas/revision-descriptor.schema.json b/schemas/revision-descriptor.schema.json new file mode 100644 index 0000000..3400eac --- /dev/null +++ b/schemas/revision-descriptor.schema.json @@ -0,0 +1,124 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://fluid.dev/schemas/revision-descriptor.schema.json", + "title": "FLUID revision descriptor", + "description": "The runtime-facing projection of a revision, deliberately small enough to be consumed deterministically by infrastructure (ArchitectureBlueprint.md section 36). This is a wire artifact: the gateway must be able to read it without any fluid-core library.", + "type": "object", + "properties": { + "revision": { + "type": "object", + "properties": { + "schema_version": { "$ref": "common.schema.json#/$defs/schemaVersion" }, + "id": { "$ref": "common.schema.json#/$defs/revisionId" }, + "interface": { "$ref": "common.schema.json#/$defs/interfaceId" }, + "state": { + "enum": ["created", "verified", "experiment", "candidate", "stable", "deprecated", "retired"], + "description": "The router refuses to route to created, failed or retired revisions (ArchitectureBlueprint.md section 5.3)." + }, + + "contract": { + "type": "object", + "properties": { + "type": { "enum": ["openapi", "graphql", "protobuf", "asyncapi", "jsonschema", "custom"] }, + "digest": { "$ref": "common.schema.json#/$defs/digest" }, + "source": { "type": "string", "description": "Where the contract artifact can be fetched." } + }, + "required": ["type", "digest"], + "additionalProperties": false + }, + + "runtime": { + "type": "object", + "description": "Out-of-process attachment: the adapter is an upstream reached over the network, so it may be implemented in any language.", + "properties": { + "upstream": { + "type": "string", + "format": "uri", + "description": "Base URL of the adapter process serving this revision." + }, + "image": { "type": "string" }, + "digest": { "$ref": "common.schema.json#/$defs/digest" }, + "timeout_ms": { "type": "integer", "exclusiveMinimum": 0, "default": 5000 }, + "retry": { + "type": "object", + "properties": { + "max_attempts": { "type": "integer", "minimum": 1, "default": 1 }, + "backoff_ms": { "type": "integer", "minimum": 0 }, + "retry_on": { "type": "array", "items": { "type": "string" } } + }, + "additionalProperties": false + }, + "circuit_breaker": { + "type": "object", + "properties": { + "failure_threshold": { "type": "integer", "minimum": 1 }, + "reset_after_ms": { "type": "integer", "exclusiveMinimum": 0 } + }, + "additionalProperties": false + } + }, + "required": ["upstream"], + "additionalProperties": false + }, + + "intent": { + "type": "object", + "description": "Which interface evolution intent governs this revision. Required: a revision with no governing intent cannot be audited.", + "properties": { + "version": { "type": "string", "minLength": 1 }, + "digest": { "$ref": "common.schema.json#/$defs/digest" } + }, + "required": ["version"], + "additionalProperties": false + }, + + "routing": { + "type": "object", + "properties": { + "eligible_cohorts": { + "type": "array", + "items": { "$ref": "common.schema.json#/$defs/cohortId" }, + "description": "Empty or absent means all cohorts are eligible." + }, + "max_traffic_share": { + "$ref": "common.schema.json#/$defs/unitInterval", + "description": "Ceiling the router enforces regardless of what a routing policy asks for." + } + }, + "additionalProperties": false + }, + + "policy": { + "type": "object", + "properties": { + "compatibility": { + "enum": ["backward_compatible", "forward_compatible", "additive", "behavioral_change", "breaking", "migration_only", "internal_only"] + }, + "security_check": { "enum": ["pending", "passed", "failed"] }, + "policy_check": { "enum": ["pending", "passed", "failed"] }, + "rollback_to": { "oneOf": [{ "$ref": "common.schema.json#/$defs/revisionId" }, { "type": "null" }] } + }, + "required": ["compatibility", "security_check"], + "additionalProperties": false + }, + + "signature": { + "type": "object", + "description": "The router accepts only signed published descriptors (ArchitectureBlueprint.md section 35). The signature covers the canonical form of this document with the signature member removed.", + "properties": { + "algorithm": { "enum": ["ed25519"] }, + "key_id": { "type": "string", "minLength": 1 }, + "value": { "type": "string", "contentEncoding": "base64" }, + "signed_at": { "$ref": "common.schema.json#/$defs/timestamp" } + }, + "required": ["algorithm", "key_id", "value"], + "additionalProperties": false + } + }, + "required": ["schema_version", "id", "interface", "state", "contract", "runtime", "intent", "policy"], + "additionalProperties": false + } + }, + "required": ["revision"], + "additionalProperties": false +} diff --git a/schemas/revision.schema.json b/schemas/revision.schema.json new file mode 100644 index 0000000..9a9d7ab --- /dev/null +++ b/schemas/revision.schema.json @@ -0,0 +1,235 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://fluid.dev/schemas/revision.schema.json", + "title": "FLUID revision record", + "description": "What deterministic interface state was built, verified, exposed and measured. Published revisions should be immutable; corrections create successors (FluidAPIStandards.md section 8).", + "type": "object", + "properties": { + "fluid_revision": { + "type": "object", + "properties": { + "schema_version": { "$ref": "common.schema.json#/$defs/schemaVersion" }, + "id": { "$ref": "common.schema.json#/$defs/revisionId" }, + "interface_id": { "$ref": "common.schema.json#/$defs/interfaceId" }, + "revision_number": { "type": "integer", "minimum": 0 }, + "parent_revision": { + "oneOf": [ + { "$ref": "common.schema.json#/$defs/revisionId" }, + { "type": "null" } + ], + "description": "Null marks genesis." + }, + "state": { + "enum": ["CREATED", "VERIFIED", "EXPERIMENT", "CANDIDATE", "STABLE", "DEPRECATED", "RETIRED"] + }, + "created_at": { "$ref": "common.schema.json#/$defs/timestamp" }, + "created_by": { "$ref": "common.schema.json#/$defs/actor" }, + + "contract": { + "type": "object", + "properties": { + "type": { "enum": ["openapi", "graphql", "protobuf", "asyncapi", "jsonschema", "custom"] }, + "version": { "type": "string" }, + "artifact_ref": { "$ref": "common.schema.json#/$defs/artifactRef" }, + "digest": { "$ref": "common.schema.json#/$defs/digest" } + }, + "required": ["type", "artifact_ref", "digest"], + "additionalProperties": false + }, + + "implementation": { + "type": "object", + "properties": { + "artifact_ref": { "$ref": "common.schema.json#/$defs/artifactRef" }, + "source_ref": { "type": "string" }, + "build_ref": { "type": "string" }, + "digest": { "$ref": "common.schema.json#/$defs/digest" } + }, + "required": ["artifact_ref", "digest"], + "additionalProperties": false + }, + + "interface_evolution_intent": { + "type": "object", + "description": "Every revision is governed by a specific intent version, so audit can ask whether the change was valid under the intent that existed at the time (ArchitectureBlueprint.md section 27).", + "properties": { + "version": { "type": "string", "minLength": 1 }, + "artifact_ref": { "$ref": "common.schema.json#/$defs/artifactRef" } + }, + "required": ["version"], + "additionalProperties": false + }, + + "originating_hypotheses": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/hypothesisId" } }, + "backend_requirements": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/backendRequirementId" } }, + + "compatibility": { + "type": "object", + "properties": { + "class": { + "enum": ["BACKWARD_COMPATIBLE", "FORWARD_COMPATIBLE", "ADDITIVE", "BEHAVIORAL_CHANGE", "BREAKING", "MIGRATION_ONLY", "INTERNAL_ONLY"] + }, + "breaking_changes": { "type": "array", "items": { "type": "string" } }, + "deprecations": { "type": "array", "items": { "type": "string" } }, + "supersedes": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/revisionId" } }, + "compatibility_evidence_refs": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/evidenceRef" } } + }, + "required": ["class"], + "additionalProperties": false + }, + + "adaptation_classes": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/adaptationClass" } }, + + "verification": { + "type": "object", + "description": "The primary safety barrier between adaptive generation and deterministic runtime (ArchitectureBlueprint.md section 15).", + "properties": { + "status": { "enum": ["PENDING", "PASSED", "FAILED"] }, + "test_refs": { "type": "array", "items": { "type": "string" } }, + "security_check": { "enum": ["PENDING", "PASSED", "FAILED"] }, + "policy_check": { "enum": ["PENDING", "PASSED", "FAILED"] } + }, + "required": ["status"], + "additionalProperties": false + }, + + "complexity": { + "type": "object", + "properties": { + "before": { "$ref": "common.schema.json#/$defs/complexityDelta" }, + "after": { "$ref": "common.schema.json#/$defs/complexityDelta" }, + "delta_score": { "type": "number" }, + "budget_status": { "enum": ["WITHIN_BUDGET", "AT_BUDGET", "OVER_BUDGET"] } + }, + "additionalProperties": false + }, + + "deployment": { + "type": "object", + "properties": { + "environments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "started_at": { "$ref": "common.schema.json#/$defs/timestamp" }, + "routing_policy_ref": { "type": "string" } + }, + "required": ["name"], + "additionalProperties": false + } + }, + "current_exposure": { + "type": "object", + "properties": { + "traffic_share": { "$ref": "common.schema.json#/$defs/unitInterval" }, + "cohorts": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/cohortId" } } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + + "fitness": { + "type": "object", + "properties": { + "baseline_revision": { "oneOf": [{ "$ref": "common.schema.json#/$defs/revisionId" }, { "type": "null" }] }, + "measurement_window": { "$ref": "common.schema.json#/$defs/observationWindow" }, + "metrics": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "baseline": { "type": ["number", "null"] }, + "current": { "type": ["number", "null"] }, + "target": { "type": ["number", "null"] }, + "guardrail": { "type": ["number", "null"] } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + + "adoption": { + "type": "object", + "description": "Adoption is evidence, not proof of quality (FluidAPIStandards.md section 32).", + "properties": { + "eligible_consumers": { "type": ["integer", "null"], "minimum": 0 }, + "active_consumers": { "type": ["integer", "null"], "minimum": 0 }, + "adoption_rate": { "oneOf": [{ "$ref": "common.schema.json#/$defs/unitInterval" }, { "type": "null" }] }, + "retained_adoption_rate": { "oneOf": [{ "$ref": "common.schema.json#/$defs/unitInterval" }, { "type": "null" }] } + }, + "additionalProperties": false + }, + + "economics": { + "type": "object", + "properties": { + "build_cost": { "type": "number", "minimum": 0 }, + "experiment_cost_to_date": { "type": "number", "minimum": 0 }, + "currency": { "type": "string" } + }, + "additionalProperties": false + }, + + "promotion": { + "type": "object", + "properties": { + "eligible": { "type": "boolean" }, + "recommended_state": { + "oneOf": [ + { "enum": ["CREATED", "VERIFIED", "EXPERIMENT", "CANDIDATE", "STABLE", "DEPRECATED", "RETIRED"] }, + { "type": "null" } + ] + }, + "recommendation_reason": { "type": ["string", "null"] }, + "authorized_by": { "oneOf": [{ "$ref": "common.schema.json#/$defs/actor" }, { "type": "null" }] }, + "authorized_at": { "oneOf": [{ "$ref": "common.schema.json#/$defs/timestamp" }, { "type": "null" }] } + }, + "additionalProperties": false + }, + + "rollback": { + "type": "object", + "properties": { + "supported": { "type": "boolean" }, + "target_revision": { "oneOf": [{ "$ref": "common.schema.json#/$defs/revisionId" }, { "type": "null" }] }, + "procedure_ref": { "type": "string" } + }, + "required": ["supported"], + "additionalProperties": false + }, + + "provenance": { + "type": "object", + "properties": { + "decision_refs": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/decisionId" } }, + "experiment_refs": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/experimentId" } }, + "telemetry_refs": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/evidenceRef" } } + }, + "additionalProperties": false + }, + + "audit": { + "type": "object", + "properties": { + "immutable_event_refs": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/eventId" } } + }, + "additionalProperties": false + } + }, + "required": [ + "schema_version", "id", "interface_id", "revision_number", "parent_revision", + "state", "contract", "implementation", "interface_evolution_intent", + "compatibility", "verification", "rollback", "provenance" + ], + "additionalProperties": false + } + }, + "required": ["fluid_revision"], + "additionalProperties": false +} diff --git a/schemas/routing-policy.schema.json b/schemas/routing-policy.schema.json new file mode 100644 index 0000000..7717197 --- /dev/null +++ b/schemas/routing-policy.schema.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://fluid.dev/schemas/routing-policy.schema.json", + "title": "FLUID routing policy", + "description": "Deterministic routing written by the experiment controller and consumed by the revision router. The controller never touches traffic itself; this separation keeps experimental intent out of the runtime decision mechanism (ArchitectureBlueprint.md section 17).", + "type": "object", + "properties": { + "routing_policy": { + "type": "object", + "properties": { + "schema_version": { "$ref": "common.schema.json#/$defs/schemaVersion" }, + "id": { "type": "string", "minLength": 1 }, + "interface": { "$ref": "common.schema.json#/$defs/interfaceId" }, + "generation": { + "type": "integer", + "minimum": 0, + "description": "Monotonic. The router ignores a policy older than the one it holds." + }, + "issued_at": { "$ref": "common.schema.json#/$defs/timestamp" }, + "issued_by": { "$ref": "common.schema.json#/$defs/actor" }, + + "default_revision": { + "$ref": "common.schema.json#/$defs/revisionId", + "description": "Where a request lands when no earlier resolution step matched." + }, + + "rules": { + "type": "array", + "description": "Evaluated in order; the first matching rule wins. Matching must be a pure function of the request and its cohort assignment.", + "items": { + "type": "object", + "properties": { + "cohort": { "$ref": "common.schema.json#/$defs/cohortId" }, + "tenant": { "type": "string" }, + "experiment": { "$ref": "common.schema.json#/$defs/experimentId" }, + "allocation": { + "type": "object", + "description": "Revision id to traffic share. Shares must sum to 1. Assignment of a given consumer must be stable across requests.", + "propertyNames": { "$ref": "common.schema.json#/$defs/revisionId" }, + "additionalProperties": { "$ref": "common.schema.json#/$defs/unitInterval" }, + "minProperties": 1 + }, + "sticky_by": { + "enum": ["consumer_id", "tenant", "correlation_id", "none"], + "default": "consumer_id", + "description": "What keeps a long-lived consumer on one side of an experiment." + } + }, + "required": ["allocation"], + "additionalProperties": false + } + }, + + "signature": { + "type": "object", + "properties": { + "algorithm": { "enum": ["ed25519"] }, + "key_id": { "type": "string", "minLength": 1 }, + "value": { "type": "string", "contentEncoding": "base64" } + }, + "required": ["algorithm", "key_id", "value"], + "additionalProperties": false + } + }, + "required": ["schema_version", "interface", "generation", "default_revision", "rules"], + "additionalProperties": false + } + }, + "required": ["routing_policy"], + "additionalProperties": false +} diff --git a/schemas/telemetry-envelope.schema.json b/schemas/telemetry-envelope.schema.json new file mode 100644 index 0000000..2a55b7f --- /dev/null +++ b/schemas/telemetry-envelope.schema.json @@ -0,0 +1,113 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://fluid.dev/schemas/telemetry-envelope.schema.json", + "title": "FLUID telemetry envelope", + "description": "The normalized interaction event. Designed for interface learning, not unrestricted behavioral capture: raw payload capture is never the default and redaction happens before an event reaches the store (ArchitectureBlueprint.md section 6.2).", + "type": "object", + "properties": { + "fluid_telemetry": { + "type": "object", + "properties": { + "schema_version": { "$ref": "common.schema.json#/$defs/schemaVersion" }, + "id": { "type": "string", "minLength": 1 }, + "occurred_at": { "$ref": "common.schema.json#/$defs/timestamp" }, + "interface_id": { "$ref": "common.schema.json#/$defs/interfaceId" }, + "kind": { + "enum": ["request", "error", "sequence", "adoption", "feedback"], + "description": "ArchitectureBlueprint.md section 6.1." + }, + + "correlation_id": { + "type": "string", + "description": "Ties an event to the response the consumer saw, and to the other events in its call chain." + }, + "consumer_ref": { + "type": "string", + "description": "Pseudonymous and stable. Never a raw end-user identifier." + }, + "cohort": { "$ref": "common.schema.json#/$defs/cohortId" }, + "revision": { "$ref": "common.schema.json#/$defs/revisionId" }, + "experiment": { "oneOf": [{ "$ref": "common.schema.json#/$defs/experimentId" }, { "type": "null" }] }, + + "resolution": { + "type": "object", + "description": "Why this revision was chosen. Revision resolution must be deterministic and auditable (ArchitectureBlueprint.md section 5.2).", + "properties": { + "reason": { + "enum": ["explicit_revision", "bound_contract", "experiment_assignment", "cohort_rule", "stable_default"] + }, + "policy_generation": { "type": "integer", "minimum": 0 } + }, + "required": ["reason"], + "additionalProperties": false + }, + + "request": { + "type": "object", + "properties": { + "operation": { "type": "string" }, + "route": { "type": "string" }, + "method": { "type": "string" }, + "status": { "type": "integer" }, + "latency_ms": { "type": "number", "minimum": 0 }, + "request_bytes": { "type": "integer", "minimum": 0 }, + "response_bytes": { "type": "integer", "minimum": 0 } + }, + "additionalProperties": false + }, + + "error": { + "type": "object", + "properties": { + "class": { + "enum": ["validation", "unknown_path", "unknown_field", "unsupported_parameter", "authorization", "backend_failure", "timeout", "policy_rejection"] + }, + "detail": { "type": "string", "description": "Redacted. Must not carry backend internals or consumer data." }, + "field": { "type": "string" } + }, + "required": ["class"], + "additionalProperties": false + }, + + "sequence": { + "type": "object", + "description": "Interaction topology is often more informative than error counts (ArchitectureBlueprint.md section 6.4).", + "properties": { + "chain_id": { "type": "string" }, + "position": { "type": "integer", "minimum": 0 }, + "pattern": { "type": "string" }, + "repeat_count": { "type": "integer", "minimum": 0 } + }, + "additionalProperties": false + }, + + "adoption": { + "type": "object", + "properties": { + "event": { "enum": ["first_use", "continued_use", "migration", "reversion", "deprecation_response"] }, + "from_revision": { "oneOf": [{ "$ref": "common.schema.json#/$defs/revisionId" }, { "type": "null" }] } + }, + "required": ["event"], + "additionalProperties": false + }, + + "feedback_ref": { "$ref": "common.schema.json#/$defs/feedbackId" }, + + "redaction": { + "type": "object", + "description": "What the privacy filter removed. Recorded so analysis knows what it cannot see.", + "properties": { + "applied": { "type": "boolean" }, + "rules": { "type": "array", "items": { "type": "string" } } + }, + "required": ["applied"], + "additionalProperties": false + } + }, + "required": ["schema_version", "id", "occurred_at", "interface_id", "kind"], + "additionalProperties": false + } + }, + "required": ["fluid_telemetry"], + "additionalProperties": false +}