Add Markitect adapter contract tests
Some checks failed
CI / Build and Test (push) Has been cancelled
CI / Lint (push) Has been cancelled

This commit is contained in:
2026-05-17 06:36:52 +02:00
parent 7e09a21c5f
commit 3d1967cb41
3 changed files with 187 additions and 1 deletions

View File

@@ -0,0 +1,75 @@
package markitect
import "github.com/netkingdom/flex-auth/pkg/api"
const (
GatewayEffectAllow = "allow"
GatewayEffectDeny = "deny"
GatewayEffectRedact = "redact"
GatewayEffectAuditDenied = "audit_denied"
)
// GatewayDecision is the Markitect-facing decision contract.
type GatewayDecision struct {
ID string `json:"id"`
Effect string `json:"effect"`
Reason string `json:"reason,omitempty"`
RuleID string `json:"rule_id,omitempty"`
PolicyVersion string `json:"policy_version,omitempty"`
Resource api.ResourceRef `json:"resource"`
ResourceMetadata map[string]any `json:"resource_metadata,omitempty"`
Subject api.SubjectRef `json:"subject"`
Obligations []api.Obligation `json:"obligations,omitempty"`
Diagnostics map[string]any `json:"diagnostics,omitempty"`
CaringDescriptor *api.CaringAccessDescriptor `json:"caring_descriptor,omitempty"`
ConformanceFindings []api.CaringConformanceFinding `json:"conformance_findings,omitempty"`
ExposureModes []api.ExposureMode `json:"exposure_modes,omitempty"`
}
// ToGatewayDecision projects a flex-auth decision envelope into the shape
// consumed by the Markitect policy gateway.
func ToGatewayDecision(decision api.DecisionEnvelope) GatewayDecision {
out := GatewayDecision{
ID: decision.ID,
Effect: gatewayEffect(decision),
Reason: decision.Reason,
RuleID: decision.MatchedRule,
PolicyVersion: decision.MatchedPolicyVersion,
Resource: decision.Resource,
ResourceMetadata: copyMap(decision.Resource.Attributes),
Subject: decision.Subject,
Obligations: append([]api.Obligation(nil), decision.Obligations...),
Diagnostics: copyMap(decision.Diagnostics),
}
if out.PolicyVersion == "" {
out.PolicyVersion = decision.Provenance.PolicyVersion
}
if decision.Caring != nil {
if decision.Caring.Descriptor != nil {
descriptor := *decision.Caring.Descriptor
out.CaringDescriptor = &descriptor
}
out.ConformanceFindings = append([]api.CaringConformanceFinding(nil), decision.Caring.ConformanceFindings...)
out.ExposureModes = append([]api.ExposureMode(nil), decision.Caring.ExposureModes...)
}
return out
}
func gatewayEffect(decision api.DecisionEnvelope) string {
if value, ok := decision.Diagnostics["markitect_effect"].(string); ok && value != "" {
return value
}
if auditDenied, ok := decision.Diagnostics["audit_denied"].(bool); ok && auditDenied {
return GatewayEffectAuditDenied
}
switch decision.Effect {
case api.DecisionEffectAllow:
return GatewayEffectAllow
case api.DecisionEffectRedact:
return GatewayEffectRedact
case api.DecisionEffectAuditOnly:
return GatewayEffectAuditDenied
default:
return GatewayEffectDeny
}
}

View File

@@ -0,0 +1,111 @@
package markitect_test
import (
"testing"
"github.com/netkingdom/flex-auth/internal/markitect"
"github.com/netkingdom/flex-auth/pkg/api"
)
func TestGatewayDecisionAllowContract(t *testing.T) {
got := markitect.ToGatewayDecision(baseEnvelope(api.DecisionEffectAllow))
if got.Effect != markitect.GatewayEffectAllow {
t.Fatalf("Effect = %q; want allow", got.Effect)
}
if got.Reason != "reader_group" || got.RuleID != "reader_group" {
t.Fatalf("reason/rule = %q/%q; want reader_group", got.Reason, got.RuleID)
}
if got.PolicyVersion != "markitect-gateway-v1" {
t.Fatalf("PolicyVersion = %q", got.PolicyVersion)
}
if got.ResourceMetadata["trust_zone"] != "internal" {
t.Fatalf("ResourceMetadata = %+v; want trust_zone", got.ResourceMetadata)
}
if got.CaringDescriptor == nil || got.CaringDescriptor.CanonicalRole != api.CanonicalRoleDoer {
t.Fatalf("CaringDescriptor = %+v; want Doer descriptor", got.CaringDescriptor)
}
}
func TestGatewayDecisionDenyContract(t *testing.T) {
got := markitect.ToGatewayDecision(baseEnvelope(api.DecisionEffectDeny))
if got.Effect != markitect.GatewayEffectDeny {
t.Fatalf("Effect = %q; want deny", got.Effect)
}
}
func TestGatewayDecisionRedactContract(t *testing.T) {
envelope := baseEnvelope(api.DecisionEffectRedact)
envelope.Obligations = []api.Obligation{
{Type: "mask_fields", Parameters: map[string]any{"fields": []string{"email"}}},
}
got := markitect.ToGatewayDecision(envelope)
if got.Effect != markitect.GatewayEffectRedact {
t.Fatalf("Effect = %q; want redact", got.Effect)
}
if len(got.Obligations) != 1 || got.Obligations[0].Type != "mask_fields" {
t.Fatalf("Obligations = %+v; want mask_fields", got.Obligations)
}
}
func TestGatewayDecisionAuditDeniedContract(t *testing.T) {
envelope := baseEnvelope(api.DecisionEffectDeny)
envelope.Diagnostics["audit_denied"] = true
got := markitect.ToGatewayDecision(envelope)
if got.Effect != markitect.GatewayEffectAuditDenied {
t.Fatalf("Effect = %q; want audit_denied", got.Effect)
}
envelope = baseEnvelope(api.DecisionEffectAuditOnly)
got = markitect.ToGatewayDecision(envelope)
if got.Effect != markitect.GatewayEffectAuditDenied {
t.Fatalf("audit_only Effect = %q; want audit_denied", got.Effect)
}
}
func baseEnvelope(effect api.DecisionEffect) api.DecisionEnvelope {
return api.DecisionEnvelope{
ID: "decision:markitect",
Effect: effect,
Reason: "reader_group",
MatchedRule: "reader_group",
MatchedPolicyVersion: "markitect-gateway-v1",
Resource: api.ResourceRef{
ID: "document:internal-note",
Type: "document",
System: markitect.SystemID,
Attributes: map[string]any{
"trust_zone": "internal",
"labels": []string{"internal"},
},
},
Subject: api.SubjectRef{ID: "user:alice"},
Diagnostics: map[string]any{
"policy_package": "markitect.gateway.check-fixtures",
},
Provenance: api.DecisionProvenance{
PolicyVersion: "markitect-gateway-v1",
},
Caring: &api.CaringDecisionMetadata{
Descriptor: &api.CaringAccessDescriptor{
ID: "descriptor:internal-document-reader",
Profile: api.CaringProfileCaring040RC2,
SubjectType: api.SubjectTypeHuman,
OrganizationRelation: api.OrganizationRelationCustomer,
CanonicalRole: api.CanonicalRoleDoer,
Scope: api.CaringScope{
Level: api.ScopeLevelResource,
ID: "document:internal-note",
},
Planes: []api.Plane{api.PlaneData},
Capabilities: []api.Capability{api.CapabilityView},
},
ExposureModes: []api.ExposureMode{api.ExposureModeMasked},
ConformanceFindings: []api.CaringConformanceFinding{
{Code: "MARKITECT-INTERNAL-READER", Severity: "info", Message: "reader group matched"},
},
},
}
}

View File

@@ -123,7 +123,7 @@ finding set, and exposure/audit behavior.
```task
id: FLEX-WP-0003-T005
status: todo
status: done
priority: medium
state_hub_task_id: "f9297b0d-69dc-495c-a650-ca671f2c59c7"
```