Generate contract types, pin fixtures to spec, add build and ADRs
Some checks failed
ci / build (push) Failing after 1m6s
Some checks failed
ci / build (push) Failing after 1m6s
Completes FLUID-WP-0002. The record types in internal/contract are generated from schemas/ by a dependency-free generator; fixtures transcribed from the spec's worked examples validate against those schemas and round-trip through the generated types with DisallowUnknownFields, so a spec change that misses the schemas fails CI rather than drifting silently. Adds identifier prefix helpers, Makefile, GitHub Actions, and five ADRs recording the Go choice, out-of-process attachment, the wire contract as boundary, the evidence store, and revision identity. 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
This commit is contained in:
parent
fbbf56df7a
commit
76912adef8
44 changed files with 4010 additions and 7 deletions
28
conformance/fixtures/README.md
Normal file
28
conformance/fixtures/README.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Schema fixtures
|
||||
|
||||
Every file here is transcribed from a worked example in `spec/`. They exist so
|
||||
that a change to the specification which is not reflected in `schemas/` fails
|
||||
the build.
|
||||
|
||||
Two deviations from the spec text are deliberate and are the only ones allowed:
|
||||
|
||||
1. **Placeholder digests.** The spec writes `sha256:<digest>`. Fixtures use a
|
||||
syntactically valid digest, because the schema constrains the format.
|
||||
2. **Placeholder artifact paths.** The spec writes `contractRef: ...`. Fixtures
|
||||
use a plausible reference.
|
||||
|
||||
Anything else that differs from the spec is a bug in the fixture or in the
|
||||
schema, not a licence to relax the schema.
|
||||
|
||||
| Fixture | Source |
|
||||
|---|---|
|
||||
| `hypothesis.yaml` | `FluidHypothesisRevisionSchema.md` §3 |
|
||||
| `revision.yaml` | `FluidHypothesisRevisionSchema.md` §8 |
|
||||
| `experiment.yaml` | `FluidHypothesisRevisionSchema.md` §12 |
|
||||
| `pressure.yaml` | `FluidHypothesisRevisionSchema.md` §13 |
|
||||
| `backend-requirement.yaml` | `FluidHypothesisRevisionSchema.md` §14 |
|
||||
| `event.yaml` | `FluidHypothesisRevisionSchema.md` §15 |
|
||||
| `revision-descriptor.yaml` | `ArchitectureBlueprint.md` §36 |
|
||||
| `routing-policy.yaml` | `ArchitectureBlueprint.md` §17 |
|
||||
| `feedback.yaml` | `FluidAPIStandards.md` §15, `ArchitectureBlueprint.md` §38 |
|
||||
| `telemetry-envelope.yaml` | `ArchitectureBlueprint.md` §6.1 |
|
||||
37
conformance/fixtures/backend-requirement.yaml
Normal file
37
conformance/fixtures/backend-requirement.yaml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
fluid_backend_requirement:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "BR-0041"
|
||||
originating_interface: "customer-context-api"
|
||||
originating_revision: "R-0081"
|
||||
originating_hypothesis: "H-0212"
|
||||
|
||||
backend_service: "payments-service"
|
||||
|
||||
capability:
|
||||
title: "Historical payment-state query"
|
||||
description: >
|
||||
Retrieve payment state as of a specified timestamp.
|
||||
|
||||
semantics:
|
||||
consistency: "snapshot"
|
||||
required_fields:
|
||||
- "payment_id"
|
||||
- "state"
|
||||
- "effective_at"
|
||||
|
||||
quality:
|
||||
p95_latency_ms: 250
|
||||
availability: 0.999
|
||||
|
||||
security:
|
||||
authorization_scope: "payments.read"
|
||||
tenant_isolation: "required"
|
||||
|
||||
expected_usage:
|
||||
requests_per_day: 120000
|
||||
|
||||
disposition:
|
||||
state: "PLANNED"
|
||||
reason: null
|
||||
target_ref: "payments-service/roadmap#412"
|
||||
24
conformance/fixtures/event.yaml
Normal file
24
conformance/fixtures/event.yaml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
fluid_event:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "EV-990281"
|
||||
occurred_at: "2026-09-09T11:31:02Z"
|
||||
|
||||
entity_type: "revision"
|
||||
entity_id: "R-000221"
|
||||
|
||||
event_type: "PROMOTION_RECOMMENDED"
|
||||
|
||||
actor:
|
||||
type: "daimon"
|
||||
id: "fluid-daimon/customer-finance-api"
|
||||
|
||||
inputs:
|
||||
- "H-000184"
|
||||
- "E-000093"
|
||||
|
||||
reason: >
|
||||
Candidate met primary success target and violated no guardrails.
|
||||
|
||||
evidence_refs:
|
||||
- "metrics:E-000093/window-4"
|
||||
41
conformance/fixtures/experiment.yaml
Normal file
41
conformance/fixtures/experiment.yaml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
fluid_experiment:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "E-000093"
|
||||
interface_id: "customer-finance-api"
|
||||
|
||||
hypothesis_refs:
|
||||
- "H-000184"
|
||||
|
||||
control_revision: "R-000220"
|
||||
candidate_revisions:
|
||||
- "R-000221"
|
||||
|
||||
cohorts:
|
||||
- "partner-integrations"
|
||||
- "coding-agents"
|
||||
|
||||
allocation:
|
||||
control: 0.72
|
||||
candidate: 0.28
|
||||
|
||||
start_at: "2026-09-05T08:00:00Z"
|
||||
planned_end_at: "2026-10-05T08:00:00Z"
|
||||
|
||||
metrics:
|
||||
primary:
|
||||
- "requests_per_completed_task"
|
||||
guardrails:
|
||||
- "p95_latency_ms"
|
||||
- "error_rate"
|
||||
- "authorization_scope_change"
|
||||
|
||||
stop_conditions:
|
||||
- "hard_guardrail_violation"
|
||||
- "security_failure"
|
||||
- "manual_stop"
|
||||
|
||||
result:
|
||||
state: "RUNNING"
|
||||
preferred_revision: null
|
||||
evidence_refs: []
|
||||
22
conformance/fixtures/feedback.yaml
Normal file
22
conformance/fixtures/feedback.yaml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
fluid_feedback:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "F-9821"
|
||||
interface_id: "customer-finance-api"
|
||||
received_at: "2026-08-28T09:12:00Z"
|
||||
cohort: "coding-agents"
|
||||
revision: "R-000220"
|
||||
|
||||
goal: "produce monthly account statement"
|
||||
missing_capability: "consolidated-period-summary"
|
||||
attempted_operations:
|
||||
- "list-transactions"
|
||||
- "get-balances"
|
||||
outcome: "capability unavailable"
|
||||
workaround:
|
||||
- "aggregate-locally"
|
||||
- "infer-closing-balance"
|
||||
confidence: 0.94
|
||||
impact:
|
||||
calls: 18
|
||||
latency_ms: 2810
|
||||
156
conformance/fixtures/hypothesis.yaml
Normal file
156
conformance/fixtures/hypothesis.yaml
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
fluid_hypothesis:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "H-000184"
|
||||
interface_id: "customer-finance-api"
|
||||
|
||||
state: "PRIORITIZED"
|
||||
|
||||
created_at: "2026-09-04T12:00:00Z"
|
||||
created_by:
|
||||
type: "daimon"
|
||||
id: "fluid-daimon/customer-finance-api"
|
||||
|
||||
title: "Provide direct latest-invoice capability"
|
||||
|
||||
observation:
|
||||
summary: >
|
||||
A significant consumer cohort retrieves invoice collections,
|
||||
sorts them by issue date, and discards all but the first item.
|
||||
evidence_refs:
|
||||
- "telemetry:invoice-pattern-2026w36"
|
||||
- "feedback:F-9821"
|
||||
affected_cohorts:
|
||||
- "partner-integrations"
|
||||
- "coding-agents"
|
||||
observation_window:
|
||||
start: "2026-08-15T00:00:00Z"
|
||||
end: "2026-09-01T00:00:00Z"
|
||||
|
||||
pressure:
|
||||
classes:
|
||||
- "successful_but_inefficient_usage"
|
||||
- "repeated_expectation_mismatch"
|
||||
severity: 0.61
|
||||
confidence: 0.88
|
||||
|
||||
explanation:
|
||||
claim: >
|
||||
Consumers treat "latest invoice" as a domain-level concept
|
||||
and the existing generic collection interface does not present
|
||||
that concept directly.
|
||||
reach:
|
||||
score: 0.42
|
||||
explains:
|
||||
- "P-1831"
|
||||
- "P-1834"
|
||||
notes: >
|
||||
Medium reach: explains several related request patterns
|
||||
but not broader financial-context pressure.
|
||||
|
||||
proposed_adaptation:
|
||||
class: "contract"
|
||||
summary: >
|
||||
Add an explicit latest-invoice retrieval capability.
|
||||
candidate_contract:
|
||||
method: "GET"
|
||||
path: "/customers/{id}/invoices/latest"
|
||||
implementation_scope: "interface_only"
|
||||
|
||||
backend_requirements:
|
||||
required: false
|
||||
requirement_refs: []
|
||||
|
||||
expected_outcomes:
|
||||
- metric: "requests_per_completed_task"
|
||||
cohort: "all"
|
||||
baseline: 2.7
|
||||
target: 1.2
|
||||
direction: "lower"
|
||||
- metric: "client_side_sort_workarounds"
|
||||
cohort: "all"
|
||||
baseline: 1.0
|
||||
target: 0.2
|
||||
direction: "lower"
|
||||
|
||||
guardrails:
|
||||
- metric: "p95_latency_ms"
|
||||
operator: "<="
|
||||
threshold: 315
|
||||
- metric: "authorization_scope_change"
|
||||
operator: "=="
|
||||
threshold: false
|
||||
- metric: "backend_query_count_delta"
|
||||
operator: "<="
|
||||
threshold: 0
|
||||
|
||||
fitness_dimensions:
|
||||
expected:
|
||||
client_utility: 0.70
|
||||
correctness: 0.00
|
||||
reliability: 0.00
|
||||
performance: 0.10
|
||||
discoverability: 0.65
|
||||
simplicity: -0.15
|
||||
compatibility: 0.00
|
||||
security: 0.00
|
||||
maintainability: -0.05
|
||||
operational_cost: 0.00
|
||||
|
||||
complexity:
|
||||
expected_delta:
|
||||
surface_area: 1
|
||||
concept_count: 0
|
||||
operation_count: 1
|
||||
semantic_overlap: 0.1
|
||||
cognitive_load: -0.2
|
||||
score: 0.18
|
||||
|
||||
risk:
|
||||
level: "LOW"
|
||||
reasons:
|
||||
- "read-only operation"
|
||||
- "no authorization expansion"
|
||||
|
||||
economics:
|
||||
estimated_experiment_cost: 18.00
|
||||
estimated_implementation_cost: 40.00
|
||||
currency: "EUR"
|
||||
expected_value_class: "MEDIUM"
|
||||
|
||||
learning_value:
|
||||
score: 0.56
|
||||
notes: >
|
||||
Useful signal for whether first-class convenience resources
|
||||
improve agent and partner integration behavior.
|
||||
|
||||
priority:
|
||||
score: 0.71
|
||||
decided_by: "optimization-policy-v3"
|
||||
|
||||
success_criteria:
|
||||
expression: >
|
||||
requests_per_completed_task <= 1.2
|
||||
AND no hard guardrail violation
|
||||
|
||||
failure_criteria:
|
||||
expression: >
|
||||
any hard guardrail violation
|
||||
OR adoption below 5% after 30 days when equally discoverable
|
||||
|
||||
candidate_revision_refs:
|
||||
- "R-000221"
|
||||
|
||||
experiment_refs:
|
||||
- "E-000093"
|
||||
|
||||
outcome:
|
||||
status: null
|
||||
summary: null
|
||||
evidence_refs: []
|
||||
|
||||
audit:
|
||||
decision_refs:
|
||||
- "D-2811"
|
||||
immutable_event_refs:
|
||||
- "EV-990218"
|
||||
38
conformance/fixtures/json/backend-requirement.json
Normal file
38
conformance/fixtures/json/backend-requirement.json
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"fluid_backend_requirement": {
|
||||
"backend_service": "payments-service",
|
||||
"capability": {
|
||||
"description": "Retrieve payment state as of a specified timestamp.\n",
|
||||
"title": "Historical payment-state query"
|
||||
},
|
||||
"disposition": {
|
||||
"reason": null,
|
||||
"state": "PLANNED",
|
||||
"target_ref": "payments-service/roadmap#412"
|
||||
},
|
||||
"expected_usage": {
|
||||
"requests_per_day": 120000
|
||||
},
|
||||
"id": "BR-0041",
|
||||
"originating_hypothesis": "H-0212",
|
||||
"originating_interface": "customer-context-api",
|
||||
"originating_revision": "R-0081",
|
||||
"quality": {
|
||||
"availability": 0.999,
|
||||
"p95_latency_ms": 250
|
||||
},
|
||||
"schema_version": "0.1",
|
||||
"security": {
|
||||
"authorization_scope": "payments.read",
|
||||
"tenant_isolation": "required"
|
||||
},
|
||||
"semantics": {
|
||||
"consistency": "snapshot",
|
||||
"required_fields": [
|
||||
"payment_id",
|
||||
"state",
|
||||
"effective_at"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
22
conformance/fixtures/json/event.json
Normal file
22
conformance/fixtures/json/event.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"fluid_event": {
|
||||
"actor": {
|
||||
"id": "fluid-daimon/customer-finance-api",
|
||||
"type": "daimon"
|
||||
},
|
||||
"entity_id": "R-000221",
|
||||
"entity_type": "revision",
|
||||
"event_type": "PROMOTION_RECOMMENDED",
|
||||
"evidence_refs": [
|
||||
"metrics:E-000093/window-4"
|
||||
],
|
||||
"id": "EV-990281",
|
||||
"inputs": [
|
||||
"H-000184",
|
||||
"E-000093"
|
||||
],
|
||||
"occurred_at": "2026-09-09T11:31:02Z",
|
||||
"reason": "Candidate met primary success target and violated no guardrails.\n",
|
||||
"schema_version": "0.1"
|
||||
}
|
||||
}
|
||||
44
conformance/fixtures/json/experiment.json
Normal file
44
conformance/fixtures/json/experiment.json
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"fluid_experiment": {
|
||||
"allocation": {
|
||||
"candidate": 0.28,
|
||||
"control": 0.72
|
||||
},
|
||||
"candidate_revisions": [
|
||||
"R-000221"
|
||||
],
|
||||
"cohorts": [
|
||||
"partner-integrations",
|
||||
"coding-agents"
|
||||
],
|
||||
"control_revision": "R-000220",
|
||||
"hypothesis_refs": [
|
||||
"H-000184"
|
||||
],
|
||||
"id": "E-000093",
|
||||
"interface_id": "customer-finance-api",
|
||||
"metrics": {
|
||||
"guardrails": [
|
||||
"p95_latency_ms",
|
||||
"error_rate",
|
||||
"authorization_scope_change"
|
||||
],
|
||||
"primary": [
|
||||
"requests_per_completed_task"
|
||||
]
|
||||
},
|
||||
"planned_end_at": "2026-10-05T08:00:00Z",
|
||||
"result": {
|
||||
"evidence_refs": [],
|
||||
"preferred_revision": null,
|
||||
"state": "RUNNING"
|
||||
},
|
||||
"schema_version": "0.1",
|
||||
"start_at": "2026-09-05T08:00:00Z",
|
||||
"stop_conditions": [
|
||||
"hard_guardrail_violation",
|
||||
"security_failure",
|
||||
"manual_stop"
|
||||
]
|
||||
}
|
||||
}
|
||||
26
conformance/fixtures/json/feedback.json
Normal file
26
conformance/fixtures/json/feedback.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"fluid_feedback": {
|
||||
"attempted_operations": [
|
||||
"list-transactions",
|
||||
"get-balances"
|
||||
],
|
||||
"cohort": "coding-agents",
|
||||
"confidence": 0.94,
|
||||
"goal": "produce monthly account statement",
|
||||
"id": "F-9821",
|
||||
"impact": {
|
||||
"calls": 18,
|
||||
"latency_ms": 2810
|
||||
},
|
||||
"interface_id": "customer-finance-api",
|
||||
"missing_capability": "consolidated-period-summary",
|
||||
"outcome": "capability unavailable",
|
||||
"received_at": "2026-08-28T09:12:00Z",
|
||||
"revision": "R-000220",
|
||||
"schema_version": "0.1",
|
||||
"workaround": [
|
||||
"aggregate-locally",
|
||||
"infer-closing-balance"
|
||||
]
|
||||
}
|
||||
}
|
||||
164
conformance/fixtures/json/hypothesis.json
Normal file
164
conformance/fixtures/json/hypothesis.json
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
{
|
||||
"fluid_hypothesis": {
|
||||
"audit": {
|
||||
"decision_refs": [
|
||||
"D-2811"
|
||||
],
|
||||
"immutable_event_refs": [
|
||||
"EV-990218"
|
||||
]
|
||||
},
|
||||
"backend_requirements": {
|
||||
"required": false,
|
||||
"requirement_refs": []
|
||||
},
|
||||
"candidate_revision_refs": [
|
||||
"R-000221"
|
||||
],
|
||||
"complexity": {
|
||||
"expected_delta": {
|
||||
"cognitive_load": -0.2,
|
||||
"concept_count": 0,
|
||||
"operation_count": 1,
|
||||
"semantic_overlap": 0.1,
|
||||
"surface_area": 1
|
||||
},
|
||||
"score": 0.18
|
||||
},
|
||||
"created_at": "2026-09-04T12:00:00Z",
|
||||
"created_by": {
|
||||
"id": "fluid-daimon/customer-finance-api",
|
||||
"type": "daimon"
|
||||
},
|
||||
"economics": {
|
||||
"currency": "EUR",
|
||||
"estimated_experiment_cost": 18.0,
|
||||
"estimated_implementation_cost": 40.0,
|
||||
"expected_value_class": "MEDIUM"
|
||||
},
|
||||
"expected_outcomes": [
|
||||
{
|
||||
"baseline": 2.7,
|
||||
"cohort": "all",
|
||||
"direction": "lower",
|
||||
"metric": "requests_per_completed_task",
|
||||
"target": 1.2
|
||||
},
|
||||
{
|
||||
"baseline": 1.0,
|
||||
"cohort": "all",
|
||||
"direction": "lower",
|
||||
"metric": "client_side_sort_workarounds",
|
||||
"target": 0.2
|
||||
}
|
||||
],
|
||||
"experiment_refs": [
|
||||
"E-000093"
|
||||
],
|
||||
"explanation": {
|
||||
"claim": "Consumers treat \"latest invoice\" as a domain-level concept and the existing generic collection interface does not present that concept directly.\n",
|
||||
"reach": {
|
||||
"explains": [
|
||||
"P-1831",
|
||||
"P-1834"
|
||||
],
|
||||
"notes": "Medium reach: explains several related request patterns but not broader financial-context pressure.\n",
|
||||
"score": 0.42
|
||||
}
|
||||
},
|
||||
"failure_criteria": {
|
||||
"expression": "any hard guardrail violation OR adoption below 5% after 30 days when equally discoverable\n"
|
||||
},
|
||||
"fitness_dimensions": {
|
||||
"expected": {
|
||||
"client_utility": 0.7,
|
||||
"compatibility": 0.0,
|
||||
"correctness": 0.0,
|
||||
"discoverability": 0.65,
|
||||
"maintainability": -0.05,
|
||||
"operational_cost": 0.0,
|
||||
"performance": 0.1,
|
||||
"reliability": 0.0,
|
||||
"security": 0.0,
|
||||
"simplicity": -0.15
|
||||
}
|
||||
},
|
||||
"guardrails": [
|
||||
{
|
||||
"metric": "p95_latency_ms",
|
||||
"operator": "<=",
|
||||
"threshold": 315
|
||||
},
|
||||
{
|
||||
"metric": "authorization_scope_change",
|
||||
"operator": "==",
|
||||
"threshold": false
|
||||
},
|
||||
{
|
||||
"metric": "backend_query_count_delta",
|
||||
"operator": "<=",
|
||||
"threshold": 0
|
||||
}
|
||||
],
|
||||
"id": "H-000184",
|
||||
"interface_id": "customer-finance-api",
|
||||
"learning_value": {
|
||||
"notes": "Useful signal for whether first-class convenience resources improve agent and partner integration behavior.\n",
|
||||
"score": 0.56
|
||||
},
|
||||
"observation": {
|
||||
"affected_cohorts": [
|
||||
"partner-integrations",
|
||||
"coding-agents"
|
||||
],
|
||||
"evidence_refs": [
|
||||
"telemetry:invoice-pattern-2026w36",
|
||||
"feedback:F-9821"
|
||||
],
|
||||
"observation_window": {
|
||||
"end": "2026-09-01T00:00:00Z",
|
||||
"start": "2026-08-15T00:00:00Z"
|
||||
},
|
||||
"summary": "A significant consumer cohort retrieves invoice collections, sorts them by issue date, and discards all but the first item.\n"
|
||||
},
|
||||
"outcome": {
|
||||
"evidence_refs": [],
|
||||
"status": null,
|
||||
"summary": null
|
||||
},
|
||||
"pressure": {
|
||||
"classes": [
|
||||
"successful_but_inefficient_usage",
|
||||
"repeated_expectation_mismatch"
|
||||
],
|
||||
"confidence": 0.88,
|
||||
"severity": 0.61
|
||||
},
|
||||
"priority": {
|
||||
"decided_by": "optimization-policy-v3",
|
||||
"score": 0.71
|
||||
},
|
||||
"proposed_adaptation": {
|
||||
"candidate_contract": {
|
||||
"method": "GET",
|
||||
"path": "/customers/{id}/invoices/latest"
|
||||
},
|
||||
"class": "contract",
|
||||
"implementation_scope": "interface_only",
|
||||
"summary": "Add an explicit latest-invoice retrieval capability.\n"
|
||||
},
|
||||
"risk": {
|
||||
"level": "LOW",
|
||||
"reasons": [
|
||||
"read-only operation",
|
||||
"no authorization expansion"
|
||||
]
|
||||
},
|
||||
"schema_version": "0.1",
|
||||
"state": "PRIORITIZED",
|
||||
"success_criteria": {
|
||||
"expression": "requests_per_completed_task <= 1.2 AND no hard guardrail violation\n"
|
||||
},
|
||||
"title": "Provide direct latest-invoice capability"
|
||||
}
|
||||
}
|
||||
25
conformance/fixtures/json/pressure.json
Normal file
25
conformance/fixtures/json/pressure.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"fluid_pressure": {
|
||||
"affected_cohorts": [
|
||||
"partner-integrations"
|
||||
],
|
||||
"class": "successful_but_inefficient_usage",
|
||||
"evidence_refs": [
|
||||
"telemetry:query-pattern-712"
|
||||
],
|
||||
"first_seen": "2026-08-15T10:11:00Z",
|
||||
"frequency": {
|
||||
"independent_consumers": 83,
|
||||
"observations": 1740
|
||||
},
|
||||
"id": "P-1831",
|
||||
"interface_id": "customer-finance-api",
|
||||
"last_seen": "2026-09-01T12:15:00Z",
|
||||
"linked_hypotheses": [
|
||||
"H-000184"
|
||||
],
|
||||
"schema_version": "0.1",
|
||||
"status": "OPEN",
|
||||
"summary": "Consumers repeatedly retrieve complete invoice collections to determine the latest invoice.\n"
|
||||
}
|
||||
}
|
||||
35
conformance/fixtures/json/revision-descriptor.json
Normal file
35
conformance/fixtures/json/revision-descriptor.json
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"revision": {
|
||||
"contract": {
|
||||
"digest": "sha256:3b1f9c2e7a4d8b6051e2c9f0a7d4b83e15c6f2a9d0b7e4c1836a5f2d9c0b7e41",
|
||||
"source": "artifact:openapi/customer-finance-api/r22",
|
||||
"type": "openapi"
|
||||
},
|
||||
"id": "R-000221",
|
||||
"intent": {
|
||||
"version": "IEI-7"
|
||||
},
|
||||
"interface": "customer-finance-api",
|
||||
"policy": {
|
||||
"compatibility": "additive",
|
||||
"policy_check": "passed",
|
||||
"rollback_to": "R-000220",
|
||||
"security_check": "passed"
|
||||
},
|
||||
"routing": {
|
||||
"eligible_cohorts": [
|
||||
"partner-integrations",
|
||||
"coding-agents"
|
||||
],
|
||||
"max_traffic_share": 0.3
|
||||
},
|
||||
"runtime": {
|
||||
"digest": "sha256:3b1f9c2e7a4d8b6051e2c9f0a7d4b83e15c6f2a9d0b7e4c1836a5f2d9c0b7e41",
|
||||
"image": "registry/fluid/customer-finance:R-42",
|
||||
"timeout_ms": 5000,
|
||||
"upstream": "http://adapter-r22.fluid-runtime.svc:8080"
|
||||
},
|
||||
"schema_version": "0.1",
|
||||
"state": "candidate"
|
||||
}
|
||||
}
|
||||
150
conformance/fixtures/json/revision.json
Normal file
150
conformance/fixtures/json/revision.json
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
{
|
||||
"fluid_revision": {
|
||||
"adaptation_classes": [
|
||||
"contract"
|
||||
],
|
||||
"adoption": {
|
||||
"active_consumers": 119,
|
||||
"adoption_rate": 0.289,
|
||||
"eligible_consumers": 412,
|
||||
"retained_adoption_rate": null
|
||||
},
|
||||
"audit": {
|
||||
"immutable_event_refs": [
|
||||
"EV-990221",
|
||||
"EV-990244",
|
||||
"EV-990281"
|
||||
]
|
||||
},
|
||||
"backend_requirements": [],
|
||||
"compatibility": {
|
||||
"breaking_changes": [],
|
||||
"class": "BACKWARD_COMPATIBLE",
|
||||
"compatibility_evidence_refs": [
|
||||
"test:compat-suite-775"
|
||||
],
|
||||
"deprecations": [],
|
||||
"supersedes": []
|
||||
},
|
||||
"complexity": {
|
||||
"after": {
|
||||
"operation_count": 40,
|
||||
"surface_area": 42
|
||||
},
|
||||
"before": {
|
||||
"operation_count": 39,
|
||||
"surface_area": 41
|
||||
},
|
||||
"budget_status": "WITHIN_BUDGET",
|
||||
"delta_score": 0.18
|
||||
},
|
||||
"contract": {
|
||||
"artifact_ref": "artifact:openapi/customer-finance-api/r22",
|
||||
"digest": "sha256:3b1f9c2e7a4d8b6051e2c9f0a7d4b83e15c6f2a9d0b7e4c1836a5f2d9c0b7e41",
|
||||
"type": "openapi",
|
||||
"version": "3.1"
|
||||
},
|
||||
"created_at": "2026-09-04T13:10:00Z",
|
||||
"created_by": {
|
||||
"id": "fluid-daimon/customer-finance-api",
|
||||
"type": "daimon"
|
||||
},
|
||||
"deployment": {
|
||||
"current_exposure": {
|
||||
"cohorts": [
|
||||
"partner-integrations",
|
||||
"coding-agents"
|
||||
],
|
||||
"traffic_share": 0.28
|
||||
},
|
||||
"environments": [
|
||||
{
|
||||
"name": "production",
|
||||
"routing_policy_ref": "route-policy:rp-118",
|
||||
"started_at": "2026-09-05T08:00:00Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
"economics": {
|
||||
"build_cost": 38.2,
|
||||
"currency": "EUR",
|
||||
"experiment_cost_to_date": 12.4
|
||||
},
|
||||
"fitness": {
|
||||
"baseline_revision": "R-000220",
|
||||
"measurement_window": {
|
||||
"end": null,
|
||||
"start": "2026-09-05T08:00:00Z"
|
||||
},
|
||||
"metrics": {
|
||||
"error_rate": {
|
||||
"baseline": 0.008,
|
||||
"current": 0.007
|
||||
},
|
||||
"p95_latency_ms": {
|
||||
"baseline": 300,
|
||||
"current": 302,
|
||||
"guardrail": 315
|
||||
},
|
||||
"requests_per_completed_task": {
|
||||
"baseline": 2.7,
|
||||
"current": 1.15,
|
||||
"target": 1.2
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "R-000221",
|
||||
"implementation": {
|
||||
"artifact_ref": "artifact:adapter/customer-finance-api/r22",
|
||||
"build_ref": "build:8871",
|
||||
"digest": "sha256:3b1f9c2e7a4d8b6051e2c9f0a7d4b83e15c6f2a9d0b7e4c1836a5f2d9c0b7e41",
|
||||
"source_ref": "git:9f4c2ab"
|
||||
},
|
||||
"interface_evolution_intent": {
|
||||
"artifact_ref": "artifact:intent/customer-finance-api/IEI-7",
|
||||
"version": "IEI-7"
|
||||
},
|
||||
"interface_id": "customer-finance-api",
|
||||
"originating_hypotheses": [
|
||||
"H-000184"
|
||||
],
|
||||
"parent_revision": "R-000220",
|
||||
"promotion": {
|
||||
"authorized_at": null,
|
||||
"authorized_by": null,
|
||||
"eligible": true,
|
||||
"recommendation_reason": "Target fitness reached with no guardrail violations.\n",
|
||||
"recommended_state": "STABLE"
|
||||
},
|
||||
"provenance": {
|
||||
"decision_refs": [
|
||||
"D-2811"
|
||||
],
|
||||
"experiment_refs": [
|
||||
"E-000093"
|
||||
],
|
||||
"telemetry_refs": [
|
||||
"telemetry:invoice-pattern-2026w36"
|
||||
]
|
||||
},
|
||||
"revision_number": 22,
|
||||
"rollback": {
|
||||
"procedure_ref": "runbook:rollback-r22",
|
||||
"supported": true,
|
||||
"target_revision": "R-000220"
|
||||
},
|
||||
"schema_version": "0.1",
|
||||
"state": "CANDIDATE",
|
||||
"verification": {
|
||||
"policy_check": "PASSED",
|
||||
"security_check": "PASSED",
|
||||
"status": "PASSED",
|
||||
"test_refs": [
|
||||
"test:unit-12081",
|
||||
"test:contract-8821",
|
||||
"test:security-610",
|
||||
"test:compat-suite-775"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
25
conformance/fixtures/json/routing-policy.json
Normal file
25
conformance/fixtures/json/routing-policy.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"routing_policy": {
|
||||
"default_revision": "R-000220",
|
||||
"generation": 118,
|
||||
"id": "rp-118",
|
||||
"interface": "customer-finance-api",
|
||||
"issued_at": "2026-09-05T08:00:00Z",
|
||||
"issued_by": {
|
||||
"id": "fluid-experiment-controller",
|
||||
"type": "system"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"allocation": {
|
||||
"R-000220": 0.8,
|
||||
"R-000221": 0.2
|
||||
},
|
||||
"cohort": "coding-agents",
|
||||
"experiment": "E-000093",
|
||||
"sticky_by": "consumer_id"
|
||||
}
|
||||
],
|
||||
"schema_version": "0.1"
|
||||
}
|
||||
}
|
||||
34
conformance/fixtures/json/telemetry-envelope.json
Normal file
34
conformance/fixtures/json/telemetry-envelope.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"fluid_telemetry": {
|
||||
"cohort": "coding-agents",
|
||||
"consumer_ref": "psu-4d1e9c",
|
||||
"correlation_id": "c-7f21ab90",
|
||||
"experiment": "E-000093",
|
||||
"id": "tl-01J8Z3XK7QW2",
|
||||
"interface_id": "customer-finance-api",
|
||||
"kind": "request",
|
||||
"occurred_at": "2026-09-05T08:14:22Z",
|
||||
"redaction": {
|
||||
"applied": true,
|
||||
"rules": [
|
||||
"drop-path-parameters",
|
||||
"pseudonymize-consumer"
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"latency_ms": 118.4,
|
||||
"method": "GET",
|
||||
"operation": "getLatestInvoice",
|
||||
"request_bytes": 0,
|
||||
"response_bytes": 812,
|
||||
"route": "/customers/{id}/invoices/latest",
|
||||
"status": 200
|
||||
},
|
||||
"resolution": {
|
||||
"policy_generation": 118,
|
||||
"reason": "experiment_assignment"
|
||||
},
|
||||
"revision": "R-000221",
|
||||
"schema_version": "0.1"
|
||||
}
|
||||
}
|
||||
29
conformance/fixtures/pressure.yaml
Normal file
29
conformance/fixtures/pressure.yaml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
fluid_pressure:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "P-1831"
|
||||
interface_id: "customer-finance-api"
|
||||
|
||||
class: "successful_but_inefficient_usage"
|
||||
|
||||
first_seen: "2026-08-15T10:11:00Z"
|
||||
last_seen: "2026-09-01T12:15:00Z"
|
||||
|
||||
affected_cohorts:
|
||||
- "partner-integrations"
|
||||
|
||||
frequency:
|
||||
observations: 1740
|
||||
independent_consumers: 83
|
||||
|
||||
summary: >
|
||||
Consumers repeatedly retrieve complete invoice collections
|
||||
to determine the latest invoice.
|
||||
|
||||
evidence_refs:
|
||||
- "telemetry:query-pattern-712"
|
||||
|
||||
status: "OPEN"
|
||||
|
||||
linked_hypotheses:
|
||||
- "H-000184"
|
||||
32
conformance/fixtures/revision-descriptor.yaml
Normal file
32
conformance/fixtures/revision-descriptor.yaml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
revision:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "R-000221"
|
||||
interface: "customer-finance-api"
|
||||
state: "candidate"
|
||||
|
||||
contract:
|
||||
type: "openapi"
|
||||
digest: "sha256:3b1f9c2e7a4d8b6051e2c9f0a7d4b83e15c6f2a9d0b7e4c1836a5f2d9c0b7e41"
|
||||
source: "artifact:openapi/customer-finance-api/r22"
|
||||
|
||||
runtime:
|
||||
upstream: "http://adapter-r22.fluid-runtime.svc:8080"
|
||||
image: "registry/fluid/customer-finance:R-42"
|
||||
digest: "sha256:3b1f9c2e7a4d8b6051e2c9f0a7d4b83e15c6f2a9d0b7e4c1836a5f2d9c0b7e41"
|
||||
timeout_ms: 5000
|
||||
|
||||
intent:
|
||||
version: "IEI-7"
|
||||
|
||||
routing:
|
||||
eligible_cohorts:
|
||||
- "partner-integrations"
|
||||
- "coding-agents"
|
||||
max_traffic_share: 0.30
|
||||
|
||||
policy:
|
||||
compatibility: "additive"
|
||||
security_check: "passed"
|
||||
policy_check: "passed"
|
||||
rollback_to: "R-000220"
|
||||
134
conformance/fixtures/revision.yaml
Normal file
134
conformance/fixtures/revision.yaml
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
fluid_revision:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "R-000221"
|
||||
interface_id: "customer-finance-api"
|
||||
|
||||
revision_number: 22
|
||||
parent_revision: "R-000220"
|
||||
|
||||
state: "CANDIDATE"
|
||||
|
||||
created_at: "2026-09-04T13:10:00Z"
|
||||
created_by:
|
||||
type: "daimon"
|
||||
id: "fluid-daimon/customer-finance-api"
|
||||
|
||||
contract:
|
||||
type: "openapi"
|
||||
version: "3.1"
|
||||
artifact_ref: "artifact:openapi/customer-finance-api/r22"
|
||||
digest: "sha256:3b1f9c2e7a4d8b6051e2c9f0a7d4b83e15c6f2a9d0b7e4c1836a5f2d9c0b7e41"
|
||||
|
||||
implementation:
|
||||
artifact_ref: "artifact:adapter/customer-finance-api/r22"
|
||||
source_ref: "git:9f4c2ab"
|
||||
build_ref: "build:8871"
|
||||
digest: "sha256:3b1f9c2e7a4d8b6051e2c9f0a7d4b83e15c6f2a9d0b7e4c1836a5f2d9c0b7e41"
|
||||
|
||||
interface_evolution_intent:
|
||||
version: "IEI-7"
|
||||
artifact_ref: "artifact:intent/customer-finance-api/IEI-7"
|
||||
|
||||
originating_hypotheses:
|
||||
- "H-000184"
|
||||
|
||||
backend_requirements: []
|
||||
|
||||
compatibility:
|
||||
class: "BACKWARD_COMPATIBLE"
|
||||
breaking_changes: []
|
||||
deprecations: []
|
||||
supersedes: []
|
||||
compatibility_evidence_refs:
|
||||
- "test:compat-suite-775"
|
||||
|
||||
adaptation_classes:
|
||||
- "contract"
|
||||
|
||||
verification:
|
||||
status: "PASSED"
|
||||
test_refs:
|
||||
- "test:unit-12081"
|
||||
- "test:contract-8821"
|
||||
- "test:security-610"
|
||||
- "test:compat-suite-775"
|
||||
security_check: "PASSED"
|
||||
policy_check: "PASSED"
|
||||
|
||||
complexity:
|
||||
before:
|
||||
surface_area: 41
|
||||
operation_count: 39
|
||||
after:
|
||||
surface_area: 42
|
||||
operation_count: 40
|
||||
delta_score: 0.18
|
||||
budget_status: "WITHIN_BUDGET"
|
||||
|
||||
deployment:
|
||||
environments:
|
||||
- name: "production"
|
||||
started_at: "2026-09-05T08:00:00Z"
|
||||
routing_policy_ref: "route-policy:rp-118"
|
||||
current_exposure:
|
||||
traffic_share: 0.28
|
||||
cohorts:
|
||||
- "partner-integrations"
|
||||
- "coding-agents"
|
||||
|
||||
fitness:
|
||||
baseline_revision: "R-000220"
|
||||
measurement_window:
|
||||
start: "2026-09-05T08:00:00Z"
|
||||
end: null
|
||||
metrics:
|
||||
requests_per_completed_task:
|
||||
baseline: 2.7
|
||||
current: 1.15
|
||||
target: 1.2
|
||||
p95_latency_ms:
|
||||
baseline: 300
|
||||
current: 302
|
||||
guardrail: 315
|
||||
error_rate:
|
||||
baseline: 0.008
|
||||
current: 0.007
|
||||
|
||||
adoption:
|
||||
eligible_consumers: 412
|
||||
active_consumers: 119
|
||||
adoption_rate: 0.289
|
||||
retained_adoption_rate: null
|
||||
|
||||
economics:
|
||||
build_cost: 38.20
|
||||
experiment_cost_to_date: 12.40
|
||||
currency: "EUR"
|
||||
|
||||
promotion:
|
||||
eligible: true
|
||||
recommended_state: "STABLE"
|
||||
recommendation_reason: >
|
||||
Target fitness reached with no guardrail violations.
|
||||
authorized_by: null
|
||||
authorized_at: null
|
||||
|
||||
rollback:
|
||||
supported: true
|
||||
target_revision: "R-000220"
|
||||
procedure_ref: "runbook:rollback-r22"
|
||||
|
||||
provenance:
|
||||
decision_refs:
|
||||
- "D-2811"
|
||||
experiment_refs:
|
||||
- "E-000093"
|
||||
telemetry_refs:
|
||||
- "telemetry:invoice-pattern-2026w36"
|
||||
|
||||
audit:
|
||||
immutable_event_refs:
|
||||
- "EV-990221"
|
||||
- "EV-990244"
|
||||
- "EV-990281"
|
||||
20
conformance/fixtures/routing-policy.yaml
Normal file
20
conformance/fixtures/routing-policy.yaml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
routing_policy:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "rp-118"
|
||||
interface: "customer-finance-api"
|
||||
generation: 118
|
||||
issued_at: "2026-09-05T08:00:00Z"
|
||||
issued_by:
|
||||
type: "system"
|
||||
id: "fluid-experiment-controller"
|
||||
|
||||
default_revision: "R-000220"
|
||||
|
||||
rules:
|
||||
- cohort: "coding-agents"
|
||||
experiment: "E-000093"
|
||||
allocation:
|
||||
R-000220: 0.80
|
||||
R-000221: 0.20
|
||||
sticky_by: "consumer_id"
|
||||
32
conformance/fixtures/telemetry-envelope.yaml
Normal file
32
conformance/fixtures/telemetry-envelope.yaml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
fluid_telemetry:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "tl-01J8Z3XK7QW2"
|
||||
occurred_at: "2026-09-05T08:14:22Z"
|
||||
interface_id: "customer-finance-api"
|
||||
kind: "request"
|
||||
|
||||
correlation_id: "c-7f21ab90"
|
||||
consumer_ref: "psu-4d1e9c"
|
||||
cohort: "coding-agents"
|
||||
revision: "R-000221"
|
||||
experiment: "E-000093"
|
||||
|
||||
resolution:
|
||||
reason: "experiment_assignment"
|
||||
policy_generation: 118
|
||||
|
||||
request:
|
||||
operation: "getLatestInvoice"
|
||||
route: "/customers/{id}/invoices/latest"
|
||||
method: "GET"
|
||||
status: 200
|
||||
latency_ms: 118.4
|
||||
request_bytes: 0
|
||||
response_bytes: 812
|
||||
|
||||
redaction:
|
||||
applied: true
|
||||
rules:
|
||||
- "drop-path-parameters"
|
||||
- "pseudonymize-consumer"
|
||||
193
conformance/validate_schemas.py
Executable file
193
conformance/validate_schemas.py
Executable file
|
|
@ -0,0 +1,193 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Validate the schema fixtures against the FLUID wire-contract schemas.
|
||||
|
||||
Every fixture is transcribed from a worked example in spec/. If the
|
||||
specification moves and the schemas do not, this fails the build.
|
||||
"""
|
||||
import json
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
import yaml
|
||||
from jsonschema import Draft202012Validator, RefResolver
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||
SCHEMAS = ROOT / "schemas"
|
||||
FIXTURES = ROOT / "conformance" / "fixtures"
|
||||
|
||||
# fixture stem -> schema stem
|
||||
PAIRS = {
|
||||
"pressure": "pressure",
|
||||
"hypothesis": "hypothesis",
|
||||
"revision": "revision",
|
||||
"experiment": "experiment",
|
||||
"event": "event",
|
||||
"feedback": "feedback",
|
||||
"backend-requirement": "backend-requirement",
|
||||
"revision-descriptor": "revision-descriptor",
|
||||
"routing-policy": "routing-policy",
|
||||
"telemetry-envelope": "telemetry-envelope",
|
||||
}
|
||||
|
||||
|
||||
def load_store():
|
||||
"""Map every schema by both its $id and its bare filename.
|
||||
|
||||
Schemas cross-reference each other as "common.schema.json#/$defs/...",
|
||||
a relative reference, so the bare filename has to resolve too.
|
||||
"""
|
||||
store = {}
|
||||
for path in SCHEMAS.glob("*.schema.json"):
|
||||
schema = json.loads(path.read_text())
|
||||
store[path.name] = schema
|
||||
if "$id" in schema:
|
||||
store[schema["$id"]] = schema
|
||||
return store
|
||||
|
||||
|
||||
# Instances that MUST be rejected. Without these the suite could pass
|
||||
# vacuously if reference resolution silently degraded to "accept anything".
|
||||
NEGATIVE = [
|
||||
(
|
||||
"pressure",
|
||||
"pressure without evidence references is not auditable",
|
||||
{"fluid_pressure": {
|
||||
"schema_version": "0.1", "id": "P-1", "interface_id": "x",
|
||||
"class": "natural_usage", "first_seen": "2026-01-01T00:00:00Z",
|
||||
"last_seen": "2026-01-01T00:00:00Z", "summary": "s",
|
||||
"evidence_refs": [], "status": "OPEN"}},
|
||||
),
|
||||
(
|
||||
"pressure",
|
||||
"pressure class outside the standard taxonomy",
|
||||
{"fluid_pressure": {
|
||||
"schema_version": "0.1", "id": "P-1", "interface_id": "x",
|
||||
"class": "vibes", "first_seen": "2026-01-01T00:00:00Z",
|
||||
"last_seen": "2026-01-01T00:00:00Z", "summary": "s",
|
||||
"evidence_refs": ["telemetry:1"], "status": "OPEN"}},
|
||||
),
|
||||
(
|
||||
"hypothesis",
|
||||
"hypothesis with no falsifiable expected outcome",
|
||||
{"fluid_hypothesis": {
|
||||
"schema_version": "0.1", "id": "H-1", "interface_id": "x",
|
||||
"state": "DRAFT", "title": "t",
|
||||
"observation": {"summary": "s", "evidence_refs": ["telemetry:1"]},
|
||||
"pressure": {"classes": ["natural_usage"]},
|
||||
"explanation": {"claim": "c"},
|
||||
"proposed_adaptation": {"class": "contract", "summary": "s"},
|
||||
"expected_outcomes": [],
|
||||
"guardrails": [],
|
||||
"complexity": {"expected_delta": {}},
|
||||
"risk": {"level": "LOW"},
|
||||
"success_criteria": {"expression": "e"}}},
|
||||
),
|
||||
(
|
||||
"experiment",
|
||||
"experiment with no stop condition is not interruptible by policy",
|
||||
{"fluid_experiment": {
|
||||
"schema_version": "0.1", "id": "E-1", "interface_id": "x",
|
||||
"hypothesis_refs": ["H-1"], "control_revision": "R-1",
|
||||
"candidate_revisions": ["R-2"],
|
||||
"allocation": {"control": 0.9, "candidate": 0.1},
|
||||
"metrics": {"primary": ["m"]},
|
||||
"stop_conditions": [],
|
||||
"result": {"state": "PLANNED"}}},
|
||||
),
|
||||
(
|
||||
"revision-descriptor",
|
||||
"descriptor with no governing intent cannot be audited",
|
||||
{"revision": {
|
||||
"schema_version": "0.1", "id": "R-1", "interface": "x",
|
||||
"state": "stable",
|
||||
"contract": {"type": "openapi", "digest": "sha256:" + "0" * 64},
|
||||
"runtime": {"upstream": "http://a:8080"},
|
||||
"policy": {"compatibility": "additive", "security_check": "passed"}}},
|
||||
),
|
||||
(
|
||||
"revision-descriptor",
|
||||
"unknown revision state",
|
||||
{"revision": {
|
||||
"schema_version": "0.1", "id": "R-1", "interface": "x",
|
||||
"state": "probably-fine",
|
||||
"contract": {"type": "openapi", "digest": "sha256:" + "0" * 64},
|
||||
"runtime": {"upstream": "http://a:8080"},
|
||||
"intent": {"version": "IEI-1"},
|
||||
"policy": {"compatibility": "additive", "security_check": "passed"}}},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def run_negative(store):
|
||||
failures = 0
|
||||
for schema_stem, why, instance in NEGATIVE:
|
||||
schema = json.loads((SCHEMAS / f"{schema_stem}.schema.json").read_text())
|
||||
resolver = RefResolver(base_uri="", referrer=schema, store=store)
|
||||
validator = Draft202012Validator(schema, resolver=resolver)
|
||||
if validator.is_valid(instance):
|
||||
print(f"FAIL rejected-case accepted: {why}")
|
||||
failures += 1
|
||||
else:
|
||||
print(f"ok rejects: {why}")
|
||||
return failures
|
||||
|
||||
|
||||
def main() -> int:
|
||||
store = load_store()
|
||||
failures = 0
|
||||
|
||||
for fixture_stem, schema_stem in sorted(PAIRS.items()):
|
||||
fixture_path = FIXTURES / f"{fixture_stem}.yaml"
|
||||
schema_path = SCHEMAS / f"{schema_stem}.schema.json"
|
||||
|
||||
if not fixture_path.exists():
|
||||
print(f"MISSING {fixture_path.relative_to(ROOT)}")
|
||||
failures += 1
|
||||
continue
|
||||
|
||||
schema = json.loads(schema_path.read_text())
|
||||
instance = yaml.safe_load(fixture_path.read_text())
|
||||
|
||||
resolver = RefResolver(base_uri="", referrer=schema, store=store)
|
||||
validator = Draft202012Validator(schema, resolver=resolver)
|
||||
|
||||
errors = sorted(validator.iter_errors(instance), key=lambda e: list(e.path))
|
||||
if errors:
|
||||
failures += 1
|
||||
print(f"FAIL {fixture_stem}")
|
||||
for err in errors:
|
||||
where = "/".join(str(p) for p in err.absolute_path) or "<root>"
|
||||
print(f" {where}: {err.message}")
|
||||
else:
|
||||
print(f"ok {fixture_stem}")
|
||||
|
||||
# A schema that no fixture exercises is a schema nothing protects.
|
||||
unexercised = {
|
||||
p.stem.removesuffix(".schema")
|
||||
for p in SCHEMAS.glob("*.schema.json")
|
||||
} - set(PAIRS.values()) - {"common"}
|
||||
for stem in sorted(unexercised):
|
||||
print(f"WARN {stem}.schema.json has no fixture")
|
||||
|
||||
# Emit JSON copies so the Go round-trip test can read fixtures without a
|
||||
# YAML dependency (the build stays free of third-party Go modules).
|
||||
json_dir = FIXTURES / "json"
|
||||
json_dir.mkdir(exist_ok=True)
|
||||
for fixture_stem in PAIRS:
|
||||
instance = yaml.safe_load((FIXTURES / f"{fixture_stem}.yaml").read_text())
|
||||
(json_dir / f"{fixture_stem}.json").write_text(
|
||||
json.dumps(instance, indent=2, sort_keys=True) + "\n"
|
||||
)
|
||||
|
||||
print()
|
||||
failures += run_negative(store)
|
||||
|
||||
if failures:
|
||||
print(f"\n{failures} check(s) failed")
|
||||
return 1
|
||||
print(f"\nAll {len(PAIRS)} fixtures validate; all {len(NEGATIVE)} rejected cases refused")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue