Implement canonical schema foundation
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 04:59:18 +02:00
parent dd0b9663c4
commit 7fdf6d63d5
29 changed files with 1905 additions and 15 deletions

View File

@@ -9,6 +9,8 @@ FLEX-WP-0005):
examples/
claims/ # key-cape lightweight-mode and Keycloak heavy-mode
# claim envelopes (P5.5)
caring/ # executable CARING descriptor, request,
# decision, registry, and audit fixtures (P2.1)
markitect/ # FlexAuthResourceManifest fixtures, decision
# fixtures, and Rego-in-Markdown policy packages
topaz/ # docker-compose + sample directory and policy

View File

@@ -0,0 +1,8 @@
# CARING examples
Small fixtures for the executable CARING 0.4.0-RC2 profile used by
`FLEX-WP-0002 P2.1`.
These are intentionally compact. They are not policy-engine fixtures yet;
they prove that the canonical descriptor, request, decision, registry, and
audit shapes can round-trip through `pkg/api`.

View File

@@ -0,0 +1,26 @@
id: descriptor:tenant-alpha-document-reader
profile: caring-0.4.0-rc2
subject_type: Human
organization_relation: Customer
canonical_role: Doer
scope:
level: Resource
id: document:internal-note
tenant: tenant:alpha
resource: document:internal-note
planes:
- Data
capabilities:
- View
exposure_modes:
- Masked
- Plaintext
conditions:
- PurposeBound
- Logged
lifecycle_state: Operate
restrictions:
- ExportBlocked
access_path: direct
metadata:
source: examples/caring

View File

@@ -0,0 +1,22 @@
{
"id": "audit:decision:tenant-alpha-internal-note",
"type": "decision",
"decision_id": "decision:tenant-alpha-internal-note",
"subject": {
"id": "user:alice",
"type": "Human",
"tenant": "tenant:alpha"
},
"resource": {
"id": "document:internal-note",
"type": "document",
"system": "markitect-tool",
"tenant": "tenant:alpha"
},
"action": "read",
"effect": "allow",
"timestamp": "2026-05-17T00:00:00Z",
"metadata": {
"profile": "caring-0.4.0-rc2"
}
}

View File

@@ -0,0 +1,41 @@
id: check:tenant-alpha-internal-note
subject:
id: user:alice
type: Human
tenant: tenant:alpha
action: read
resource:
id: document:internal-note
type: document
system: markitect-tool
tenant: tenant:alpha
context:
purpose: knowledge-base-read
assurance:
mfa: true
caring_context:
id: descriptor:tenant-alpha-document-reader
profile: caring-0.4.0-rc2
subject_type: Human
organization_relation: Customer
canonical_role: Doer
scope:
level: Resource
id: document:internal-note
tenant: tenant:alpha
resource: document:internal-note
planes:
- Data
capabilities:
- View
exposure_modes:
- Masked
- Plaintext
conditions:
- PurposeBound
- Logged
lifecycle_state: Operate
restrictions:
- ExportBlocked
access_path: direct
policy_version: markitect.documents.v1

View File

@@ -0,0 +1,69 @@
{
"id": "decision:tenant-alpha-internal-note",
"request_id": "check:tenant-alpha-internal-note",
"effect": "allow",
"reason": "reader_relation",
"matched_policy_version": "markitect.documents.v1",
"matched_rule": "allow_document_read",
"resource": {
"id": "document:internal-note",
"type": "document",
"system": "markitect-tool",
"tenant": "tenant:alpha"
},
"subject": {
"id": "user:alice",
"type": "Human",
"tenant": "tenant:alpha"
},
"obligations": [
{
"type": "log_access",
"parameters": {
"level": "standard"
}
}
],
"diagnostics": {
"policy_package": "examples/caring"
},
"provenance": {
"evaluator": "flex-auth",
"mode": "standalone",
"policy_package": "markitect.documents",
"policy_version": "v1",
"decision_time": "2026-05-17T00:00:00Z"
},
"caring": {
"profile": "caring-0.4.0-rc2",
"descriptor": {
"id": "descriptor:tenant-alpha-document-reader",
"profile": "caring-0.4.0-rc2",
"subject_type": "Human",
"organization_relation": "Customer",
"canonical_role": "Doer",
"scope": {
"level": "Resource",
"id": "document:internal-note",
"tenant": "tenant:alpha",
"resource": "document:internal-note"
},
"planes": ["Data"],
"capabilities": ["View"],
"exposure_modes": ["Masked", "Plaintext"],
"conditions": ["PurposeBound", "Logged"],
"lifecycle_state": "Operate",
"restrictions": ["ExportBlocked"],
"access_path": "direct"
},
"restrictions_evaluated": ["ExportBlocked"],
"exposure_modes": ["Masked", "Plaintext"],
"conformance_findings": [
{
"code": "CARING-EXPORT-SEPARATION",
"severity": "info",
"message": "View is allowed, but Exportable exposure remains separately blocked."
}
]
}
}

View File

@@ -0,0 +1,45 @@
id: fixture:markitect-internal-read-allow
request:
id: check:tenant-alpha-internal-note
subject:
id: user:alice
type: Human
tenant: tenant:alpha
action: read
resource:
id: document:internal-note
type: document
system: markitect-tool
tenant: tenant:alpha
caring_context:
id: descriptor:tenant-alpha-document-reader
profile: caring-0.4.0-rc2
subject_type: Human
organization_relation: Customer
canonical_role: Doer
scope:
level: Resource
id: document:internal-note
tenant: tenant:alpha
resource: document:internal-note
planes:
- Data
capabilities:
- View
exposure_modes:
- Masked
- Plaintext
conditions:
- PurposeBound
- Logged
restrictions:
- ExportBlocked
expect:
effect: allow
reason: reader_relation
conformance_findings:
- code: CARING-EXPORT-SEPARATION
severity: info
message: View is allowed, but Exportable exposure remains separately blocked.
metadata:
source: examples/caring

View File

@@ -0,0 +1,29 @@
id: markitect.documents.internal-read
name: Markitect internal document read
version: v1
status: draft
package: flexauth.markitect.documents
caring:
profile: caring-0.4.0-rc2
canonical_roles:
- Doer
organization_relations:
- Customer
scopes:
- level: Resource
id: document:internal-note
tenant: tenant:alpha
planes:
- Data
capabilities:
- View
exposure_modes:
- Masked
- Plaintext
conditions:
- PurposeBound
- Logged
restrictions:
- ExportBlocked
metadata:
source: examples/caring

View File

@@ -0,0 +1,32 @@
id: rel:alice-reader-internal-note
system: markitect-tool
subject: group:platform-architecture
relation: reader
object: document:internal-note
tenant: tenant:alpha
conditions:
- Logged
caring:
id: descriptor:tenant-alpha-document-reader
profile: caring-0.4.0-rc2
subject_type: Group
organization_relation: Customer
canonical_role: Doer
scope:
level: Resource
id: document:internal-note
tenant: tenant:alpha
resource: document:internal-note
planes:
- Data
capabilities:
- View
exposure_modes:
- Masked
- Plaintext
conditions:
- Logged
restrictions:
- ExportBlocked
provenance:
source: examples/caring

View File

@@ -0,0 +1,22 @@
id: subjects:tenant-alpha
subjects:
- id: user:alice
type: Human
display_name: Alice Example
organization_relation: Customer
roles:
- Doer
groups:
- group:platform-architecture
tenant: tenant:alpha
groups:
- id: group:platform-architecture
display_name: Platform Architecture
members:
- user:alice
tenant: tenant:alpha
tenants:
- id: tenant:alpha
name: Tenant Alpha
metadata:
source: examples/caring

View File

@@ -0,0 +1,50 @@
package policy_test
import (
"os"
"path/filepath"
"testing"
"gopkg.in/yaml.v3"
"github.com/netkingdom/flex-auth/pkg/api"
)
func TestPolicyPackageMetadataParses(t *testing.T) {
var metadata api.PolicyPackageMetadata
loadYAML(t, filepath.Join("..", "..", "examples", "caring", "policy_package.yaml"), &metadata)
if metadata.Caring.Profile != api.CaringProfileCaring040RC2 {
t.Fatalf("metadata.Caring.Profile = %q; want %q", metadata.Caring.Profile, api.CaringProfileCaring040RC2)
}
if len(metadata.Caring.Capabilities) != 1 || metadata.Caring.Capabilities[0] != api.CapabilityView {
t.Errorf("metadata.Caring.Capabilities = %v; want [View]", metadata.Caring.Capabilities)
}
if len(metadata.Caring.Restrictions) != 1 || metadata.Caring.Restrictions[0] != api.RestrictionExportBlocked {
t.Errorf("metadata.Caring.Restrictions = %v; want [ExportBlocked]", metadata.Caring.Restrictions)
}
}
func TestPolicyFixtureParses(t *testing.T) {
var fixture api.PolicyFixture
loadYAML(t, filepath.Join("..", "..", "examples", "caring", "policy_fixture.yaml"), &fixture)
if fixture.Expect.Effect != api.DecisionEffectAllow {
t.Errorf("fixture.Expect.Effect = %q; want allow", fixture.Expect.Effect)
}
if fixture.Request.CaringContext == nil {
t.Fatal("fixture.Request.CaringContext is nil")
}
}
func loadYAML(t *testing.T, path string, out any) {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
if err := yaml.Unmarshal(data, out); err != nil {
t.Fatalf("unmarshal %s: %v", path, err)
}
}

View File

@@ -0,0 +1,43 @@
package registry_test
import (
"os"
"path/filepath"
"testing"
"gopkg.in/yaml.v3"
"github.com/netkingdom/flex-auth/pkg/api"
)
func TestRegistryManifestsParse(t *testing.T) {
var subjects api.SubjectManifest
loadYAML(t, filepath.Join("..", "..", "examples", "caring", "subject_manifest.yaml"), &subjects)
if len(subjects.Subjects) != 1 {
t.Fatalf("Subjects len = %d; want 1", len(subjects.Subjects))
}
if subjects.Subjects[0].Roles[0] != api.CanonicalRoleDoer {
t.Errorf("Subject role = %q; want Doer", subjects.Subjects[0].Roles[0])
}
var fact api.RelationshipFact
loadYAML(t, filepath.Join("..", "..", "examples", "caring", "relationship_fact.yaml"), &fact)
if fact.Subject != "group:platform-architecture" || fact.Object != "document:internal-note" {
t.Fatalf("relationship fact did not parse as expected: %+v", fact)
}
if fact.Caring == nil || fact.Caring.Profile != api.CaringProfileCaring040RC2 {
t.Fatalf("fact.Caring = %+v; want CARING profile descriptor", fact.Caring)
}
}
func loadYAML(t *testing.T, path string, out any) {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
if err := yaml.Unmarshal(data, out); err != nil {
t.Fatalf("unmarshal %s: %v", path, err)
}
}

249
pkg/api/canonical.go Normal file
View File

@@ -0,0 +1,249 @@
package api
// ProtectedSystemManifest describes a system that delegates authorization to
// flex-auth.
type ProtectedSystemManifest struct {
ID string `json:"id" yaml:"id"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
ResourceTypes []ResourceType `json:"resource_types,omitempty" yaml:"resource_types,omitempty"`
Actions []ActionDefinition `json:"actions,omitempty" yaml:"actions,omitempty"`
CaringProfiles []string `json:"caring_profiles,omitempty" yaml:"caring_profiles,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// ResourceType describes a resource namespace entry owned by a protected system.
type ResourceType struct {
Name string `json:"name" yaml:"name"`
ParentTypes []string `json:"parent_types,omitempty" yaml:"parent_types,omitempty"`
ScopeLevel ScopeLevel `json:"scope_level,omitempty" yaml:"scope_level,omitempty"`
Planes []Plane `json:"planes,omitempty" yaml:"planes,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// ActionDefinition maps a protected-system action to CARING capabilities.
type ActionDefinition struct {
Name string `json:"name" yaml:"name"`
Capabilities []Capability `json:"capabilities,omitempty" yaml:"capabilities,omitempty"`
Planes []Plane `json:"planes,omitempty" yaml:"planes,omitempty"`
ExposureModes []ExposureMode `json:"exposure_modes,omitempty" yaml:"exposure_modes,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// SubjectManifest declares subjects, groups, teams, and tenants for local
// registry loading.
type SubjectManifest struct {
ID string `json:"id" yaml:"id"`
Subjects []Subject `json:"subjects,omitempty" yaml:"subjects,omitempty"`
Groups []Group `json:"groups,omitempty" yaml:"groups,omitempty"`
Teams []Team `json:"teams,omitempty" yaml:"teams,omitempty"`
Tenants []Tenant `json:"tenants,omitempty" yaml:"tenants,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// Subject is a human, service, automation, agent, or other acting identity.
type Subject struct {
ID string `json:"id" yaml:"id"`
Type SubjectType `json:"type" yaml:"type"`
DisplayName string `json:"display_name,omitempty" yaml:"display_name,omitempty"`
OrganizationRelation OrganizationRelation `json:"organization_relation,omitempty" yaml:"organization_relation,omitempty"`
Roles []CanonicalRole `json:"roles,omitempty" yaml:"roles,omitempty"`
Groups []string `json:"groups,omitempty" yaml:"groups,omitempty"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
Claims map[string]any `json:"claims,omitempty" yaml:"claims,omitempty"`
CaringDescriptors []CaringAccessDescriptor `json:"caring_descriptors,omitempty" yaml:"caring_descriptors,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// Group is an assignment convenience, not a canonical role.
type Group struct {
ID string `json:"id" yaml:"id"`
DisplayName string `json:"display_name,omitempty" yaml:"display_name,omitempty"`
Members []string `json:"members,omitempty" yaml:"members,omitempty"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
CaringDescriptors []CaringAccessDescriptor `json:"caring_descriptors,omitempty" yaml:"caring_descriptors,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// Team is a group-like ownership unit used by protected systems.
type Team struct {
ID string `json:"id" yaml:"id"`
DisplayName string `json:"display_name,omitempty" yaml:"display_name,omitempty"`
Members []string `json:"members,omitempty" yaml:"members,omitempty"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
CaringDescriptors []CaringAccessDescriptor `json:"caring_descriptors,omitempty" yaml:"caring_descriptors,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// Tenant is a structural isolation boundary.
type Tenant struct {
ID string `json:"id" yaml:"id"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// RelationshipFact records a relation between subjects, groups, teams, tenants,
// and resources.
type RelationshipFact struct {
ID string `json:"id" yaml:"id"`
System string `json:"system,omitempty" yaml:"system,omitempty"`
Subject string `json:"subject" yaml:"subject"`
Relation string `json:"relation" yaml:"relation"`
Object string `json:"object" yaml:"object"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
Conditions []Condition `json:"conditions,omitempty" yaml:"conditions,omitempty"`
Caring *CaringAccessDescriptor `json:"caring,omitempty" yaml:"caring,omitempty"`
Provenance map[string]any `json:"provenance,omitempty" yaml:"provenance,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// PolicyPackageMetadata is the frontmatter contract for Rego-in-Markdown
// policy packages.
type PolicyPackageMetadata struct {
ID string `json:"id" yaml:"id"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Version string `json:"version" yaml:"version"`
Status string `json:"status,omitempty" yaml:"status,omitempty"`
Package string `json:"package" yaml:"package"`
Caring CaringPolicyMetadata `json:"caring" yaml:"caring"`
Activation map[string]any `json:"activation,omitempty" yaml:"activation,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// CaringPolicyMetadata declares the CARING envelope a policy governs.
type CaringPolicyMetadata struct {
Profile string `json:"profile" yaml:"profile"`
CanonicalRoles []CanonicalRole `json:"canonical_roles,omitempty" yaml:"canonical_roles,omitempty"`
OrganizationRelations []OrganizationRelation `json:"organization_relations,omitempty" yaml:"organization_relations,omitempty"`
Scopes []CaringScope `json:"scopes,omitempty" yaml:"scopes,omitempty"`
Planes []Plane `json:"planes,omitempty" yaml:"planes,omitempty"`
Capabilities []Capability `json:"capabilities,omitempty" yaml:"capabilities,omitempty"`
ExposureModes []ExposureMode `json:"exposure_modes,omitempty" yaml:"exposure_modes,omitempty"`
Conditions []Condition `json:"conditions,omitempty" yaml:"conditions,omitempty"`
Restrictions []Restriction `json:"restrictions,omitempty" yaml:"restrictions,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// PolicyFixture binds a check request to an expected decision.
type PolicyFixture struct {
ID string `json:"id" yaml:"id"`
Request CheckRequest `json:"request" yaml:"request"`
Expect DecisionExpectation `json:"expect" yaml:"expect"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// DecisionExpectation is the compact fixture expectation for policy tests.
type DecisionExpectation struct {
Effect DecisionEffect `json:"effect" yaml:"effect"`
Reason string `json:"reason,omitempty" yaml:"reason,omitempty"`
Obligations []Obligation `json:"obligations,omitempty" yaml:"obligations,omitempty"`
ConformanceFindings []CaringConformanceFinding `json:"conformance_findings,omitempty" yaml:"conformance_findings,omitempty"`
}
// CheckRequest is the stable protected-system-facing decision request.
type CheckRequest struct {
ID string `json:"id,omitempty" yaml:"id,omitempty"`
Subject SubjectRef `json:"subject" yaml:"subject"`
Action string `json:"action" yaml:"action"`
Resource ResourceRef `json:"resource" yaml:"resource"`
Context map[string]any `json:"context,omitempty" yaml:"context,omitempty"`
CaringContext *CaringAccessDescriptor `json:"caring_context,omitempty" yaml:"caring_context,omitempty"`
PolicyVersion string `json:"policy_version,omitempty" yaml:"policy_version,omitempty"`
}
// BatchCheckRequest evaluates one subject/action against multiple resources.
type BatchCheckRequest struct {
ID string `json:"id,omitempty" yaml:"id,omitempty"`
Subject SubjectRef `json:"subject" yaml:"subject"`
Action string `json:"action" yaml:"action"`
Resources []ResourceRef `json:"resources" yaml:"resources"`
Context map[string]any `json:"context,omitempty" yaml:"context,omitempty"`
PolicyVersion string `json:"policy_version,omitempty" yaml:"policy_version,omitempty"`
}
// SubjectRef is a normalized subject reference in request and decision shapes.
type SubjectRef struct {
ID string `json:"id" yaml:"id"`
Type SubjectType `json:"type,omitempty" yaml:"type,omitempty"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
Attributes map[string]any `json:"attributes,omitempty" yaml:"attributes,omitempty"`
}
// ResourceRef is a normalized resource reference in request and decision shapes.
type ResourceRef struct {
ID string `json:"id" yaml:"id"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
System string `json:"system,omitempty" yaml:"system,omitempty"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
Attributes map[string]any `json:"attributes,omitempty" yaml:"attributes,omitempty"`
}
// DecisionEffect is the stable decision outcome vocabulary.
type DecisionEffect string
const (
DecisionEffectAllow DecisionEffect = "allow"
DecisionEffectDeny DecisionEffect = "deny"
DecisionEffectRedact DecisionEffect = "redact"
DecisionEffectAuditOnly DecisionEffect = "audit_only"
DecisionEffectNotApplicable DecisionEffect = "not_applicable"
)
// DecisionEnvelope is the stable response produced by standalone and delegated
// evaluators.
type DecisionEnvelope struct {
ID string `json:"id" yaml:"id"`
RequestID string `json:"request_id,omitempty" yaml:"request_id,omitempty"`
Effect DecisionEffect `json:"effect" yaml:"effect"`
Reason string `json:"reason,omitempty" yaml:"reason,omitempty"`
MatchedPolicyVersion string `json:"matched_policy_version,omitempty" yaml:"matched_policy_version,omitempty"`
MatchedRule string `json:"matched_rule,omitempty" yaml:"matched_rule,omitempty"`
Resource ResourceRef `json:"resource" yaml:"resource"`
Subject SubjectRef `json:"subject" yaml:"subject"`
Obligations []Obligation `json:"obligations,omitempty" yaml:"obligations,omitempty"`
Diagnostics map[string]any `json:"diagnostics,omitempty" yaml:"diagnostics,omitempty"`
Provenance DecisionProvenance `json:"provenance" yaml:"provenance"`
Caring *CaringDecisionMetadata `json:"caring,omitempty" yaml:"caring,omitempty"`
}
// Obligation describes a follow-up behavior required by a decision.
type Obligation struct {
Type string `json:"type" yaml:"type"`
Parameters map[string]any `json:"parameters,omitempty" yaml:"parameters,omitempty"`
}
// DecisionProvenance captures evaluator and policy provenance.
type DecisionProvenance struct {
Evaluator string `json:"evaluator" yaml:"evaluator"`
Mode string `json:"mode" yaml:"mode"`
PolicyPackage string `json:"policy_package,omitempty" yaml:"policy_package,omitempty"`
PolicyVersion string `json:"policy_version,omitempty" yaml:"policy_version,omitempty"`
DirectoryETag string `json:"directory_etag,omitempty" yaml:"directory_etag,omitempty"`
DecisionTime string `json:"decision_time,omitempty" yaml:"decision_time,omitempty"`
}
// CaringDecisionMetadata carries CARING descriptor and conformance details in
// a decision envelope.
type CaringDecisionMetadata struct {
Profile string `json:"profile" yaml:"profile"`
Descriptor *CaringAccessDescriptor `json:"descriptor,omitempty" yaml:"descriptor,omitempty"`
RestrictionsEvaluated []Restriction `json:"restrictions_evaluated,omitempty" yaml:"restrictions_evaluated,omitempty"`
ExposureModes []ExposureMode `json:"exposure_modes,omitempty" yaml:"exposure_modes,omitempty"`
DerivedCapabilities []CaringDerivedCapability `json:"derived_capabilities,omitempty" yaml:"derived_capabilities,omitempty"`
ConformanceFindings []CaringConformanceFinding `json:"conformance_findings,omitempty" yaml:"conformance_findings,omitempty"`
ExposureEvent *CaringExposureEvent `json:"exposure_event,omitempty" yaml:"exposure_event,omitempty"`
}
// AuditEvent is the local log shape for decisions and exposure events.
type AuditEvent struct {
ID string `json:"id" yaml:"id"`
Type string `json:"type" yaml:"type"`
DecisionID string `json:"decision_id,omitempty" yaml:"decision_id,omitempty"`
Subject SubjectRef `json:"subject" yaml:"subject"`
Resource ResourceRef `json:"resource,omitempty" yaml:"resource,omitempty"`
Action string `json:"action,omitempty" yaml:"action,omitempty"`
Effect DecisionEffect `json:"effect,omitempty" yaml:"effect,omitempty"`
Timestamp string `json:"timestamp,omitempty" yaml:"timestamp,omitempty"`
ExposureEvent *CaringExposureEvent `json:"exposure_event,omitempty" yaml:"exposure_event,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}

130
pkg/api/canonical_test.go Normal file
View File

@@ -0,0 +1,130 @@
package api_test
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"gopkg.in/yaml.v3"
"github.com/netkingdom/flex-auth/pkg/api"
)
func TestCaringAccessDescriptorExampleParses(t *testing.T) {
var got api.CaringAccessDescriptor
loadYAML(t, filepath.Join("..", "..", "examples", "caring", "access_descriptor.yaml"), &got)
if got.Profile != api.CaringProfileCaring040RC2 {
t.Fatalf("Profile = %q; want %q", got.Profile, api.CaringProfileCaring040RC2)
}
if got.CanonicalRole != api.CanonicalRoleDoer {
t.Errorf("CanonicalRole = %q; want Doer", got.CanonicalRole)
}
if got.Scope.Level != api.ScopeLevelResource || got.Scope.Tenant != "tenant:alpha" {
t.Errorf("Scope = %+v; want resource scope in tenant:alpha", got.Scope)
}
if len(got.Restrictions) != 1 || got.Restrictions[0] != api.RestrictionExportBlocked {
t.Errorf("Restrictions = %v; want [ExportBlocked]", got.Restrictions)
}
}
func TestCheckRequestExampleParses(t *testing.T) {
var got api.CheckRequest
loadYAML(t, filepath.Join("..", "..", "examples", "caring", "check_request.yaml"), &got)
if got.Subject.ID != "user:alice" {
t.Errorf("Subject.ID = %q; want user:alice", got.Subject.ID)
}
if got.Resource.ID != "document:internal-note" {
t.Errorf("Resource.ID = %q; want document:internal-note", got.Resource.ID)
}
if got.CaringContext == nil {
t.Fatal("CaringContext is nil")
}
if got.CaringContext.AccessPath != api.AccessPathDirect {
t.Errorf("CaringContext.AccessPath = %q; want direct", got.CaringContext.AccessPath)
}
}
func TestRegistryExamplesParse(t *testing.T) {
var subjects api.SubjectManifest
loadYAML(t, filepath.Join("..", "..", "examples", "caring", "subject_manifest.yaml"), &subjects)
if len(subjects.Subjects) != 1 || subjects.Subjects[0].Type != api.SubjectTypeHuman {
t.Fatalf("subjects did not parse as expected: %+v", subjects.Subjects)
}
var relationship api.RelationshipFact
loadYAML(t, filepath.Join("..", "..", "examples", "caring", "relationship_fact.yaml"), &relationship)
if relationship.Caring == nil {
t.Fatal("RelationshipFact.Caring is nil")
}
if relationship.Caring.SubjectType != api.SubjectTypeGroup {
t.Errorf("RelationshipFact.Caring.SubjectType = %q; want Group", relationship.Caring.SubjectType)
}
}
func TestDecisionAndAuditExamplesParse(t *testing.T) {
var decision api.DecisionEnvelope
loadJSON(t, filepath.Join("..", "..", "examples", "caring", "decision_envelope.json"), &decision)
if decision.Effect != api.DecisionEffectAllow {
t.Errorf("Decision.Effect = %q; want allow", decision.Effect)
}
if decision.Caring == nil || decision.Caring.Profile != api.CaringProfileCaring040RC2 {
t.Fatalf("Decision.Caring = %+v; want CARING profile metadata", decision.Caring)
}
if len(decision.Caring.ConformanceFindings) != 1 {
t.Errorf("ConformanceFindings len = %d; want 1", len(decision.Caring.ConformanceFindings))
}
var audit api.AuditEvent
loadJSON(t, filepath.Join("..", "..", "examples", "caring", "audit_event.json"), &audit)
if audit.DecisionID != decision.ID {
t.Errorf("Audit.DecisionID = %q; want %q", audit.DecisionID, decision.ID)
}
}
func TestSchemaFilesAreJSON(t *testing.T) {
schemaDir := filepath.Join("..", "..", "schemas")
entries, err := os.ReadDir(schemaDir)
if err != nil {
t.Fatalf("read schema dir: %v", err)
}
for _, entry := range entries {
if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" {
continue
}
t.Run(entry.Name(), func(t *testing.T) {
var got map[string]any
loadJSON(t, filepath.Join(schemaDir, entry.Name()), &got)
if got["$schema"] == "" || got["$id"] == "" {
t.Fatalf("%s missing $schema or $id", entry.Name())
}
})
}
}
func loadYAML(t *testing.T, path string, out any) {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
if err := yaml.Unmarshal(data, out); err != nil {
t.Fatalf("unmarshal %s: %v", path, err)
}
}
func loadJSON(t *testing.T, path string, out any) {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
if err := json.Unmarshal(data, out); err != nil {
t.Fatalf("unmarshal %s: %v", path, err)
}
}

335
pkg/api/caring.go Normal file
View File

@@ -0,0 +1,335 @@
package api
// CaringProfileCaring040RC2 is the executable profile identifier for the
// CARING 0.4.0-RC2 standard pinned by flex-auth.
const CaringProfileCaring040RC2 = "caring-0.4.0-rc2"
// SubjectType is the CARING subject dimension.
type SubjectType string
const (
SubjectTypeHuman SubjectType = "Human"
SubjectTypeGroup SubjectType = "Group"
SubjectTypeOrganization SubjectType = "Organization"
SubjectTypeService SubjectType = "Service"
SubjectTypeAutomation SubjectType = "Automation"
SubjectTypeAgent SubjectType = "Agent"
SubjectTypeSystem SubjectType = "System"
SubjectTypeDevice SubjectType = "Device"
SubjectTypeProcess SubjectType = "Process"
SubjectTypeAnonymous SubjectType = "Anonymous"
SubjectTypeUnknown SubjectType = "Unknown"
)
// OrganizationRelation is the CARING organization-relation dimension.
type OrganizationRelation string
const (
OrganizationRelationVendor OrganizationRelation = "Vendor"
OrganizationRelationServiceProvider OrganizationRelation = "ServiceProvider"
OrganizationRelationDistributor OrganizationRelation = "Distributor"
OrganizationRelationConsultant OrganizationRelation = "Consultant"
OrganizationRelationCustomer OrganizationRelation = "Customer"
OrganizationRelationCommunity OrganizationRelation = "Community"
OrganizationRelationAuthority OrganizationRelation = "Authority"
OrganizationRelationUnknown OrganizationRelation = "Unknown"
)
// CanonicalRole is the CARING lifecycle responsibility posture.
type CanonicalRole string
const (
CanonicalRoleCreator CanonicalRole = "Creator"
CanonicalRoleBuilder CanonicalRole = "Builder"
CanonicalRoleVerifier CanonicalRole = "Verifier"
CanonicalRoleMaintainer CanonicalRole = "Maintainer"
CanonicalRoleIntegrator CanonicalRole = "Integrator"
CanonicalRoleOperator CanonicalRole = "Operator"
CanonicalRoleManager CanonicalRole = "Manager"
CanonicalRoleCoach CanonicalRole = "Coach"
CanonicalRoleDoer CanonicalRole = "Doer"
)
// ScopeLevel is the CARING scope ladder.
type ScopeLevel string
const (
ScopeLevelEcosystem ScopeLevel = "Ecosystem"
ScopeLevelProduct ScopeLevel = "Product"
ScopeLevelPlatform ScopeLevel = "Platform"
ScopeLevelCluster ScopeLevel = "Cluster"
ScopeLevelEnvironment ScopeLevel = "Environment"
ScopeLevelTenant ScopeLevel = "Tenant"
ScopeLevelNamespace ScopeLevel = "Namespace"
ScopeLevelDomain ScopeLevel = "Domain"
ScopeLevelWorkspace ScopeLevel = "Workspace"
ScopeLevelProject ScopeLevel = "Project"
ScopeLevelProcess ScopeLevel = "Process"
ScopeLevelDataset ScopeLevel = "Dataset"
ScopeLevelResource ScopeLevel = "Resource"
ScopeLevelSubresource ScopeLevel = "Subresource"
ScopeLevelRecord ScopeLevel = "Record"
ScopeLevelField ScopeLevel = "Field"
ScopeLevelAction ScopeLevel = "Action"
)
// Plane is the CARING access-surface dimension.
type Plane string
const (
PlaneIntent Plane = "Intent"
PlaneBuild Plane = "Build"
PlaneRuntime Plane = "Runtime"
PlaneExecution Plane = "Execution"
PlaneConfiguration Plane = "Configuration"
PlaneData Plane = "Data"
PlaneIdentity Plane = "Identity"
PlanePolicy Plane = "Policy"
PlaneSecret Plane = "Secret"
PlaneAudit Plane = "Audit"
PlaneCommercial Plane = "Commercial"
PlaneCommunity Plane = "Community"
)
// Capability is a CARING capability verb.
type Capability string
const (
CapabilityView Capability = "View"
CapabilityViewCollection Capability = "ViewCollection"
CapabilityObserve Capability = "Observe"
CapabilityCreate Capability = "Create"
CapabilityEditOwn Capability = "EditOwn"
CapabilityEditAssigned Capability = "EditAssigned"
CapabilityEditAny Capability = "EditAny"
CapabilityDeleteOwn Capability = "DeleteOwn"
CapabilityDeleteAny Capability = "DeleteAny"
CapabilityBulkDelete Capability = "BulkDelete"
CapabilitySubmit Capability = "Submit"
CapabilityComment Capability = "Comment"
CapabilityReview Capability = "Review"
CapabilityApprove Capability = "Approve"
CapabilityReject Capability = "Reject"
CapabilityPublish Capability = "Publish"
CapabilityArchive Capability = "Archive"
CapabilityRestore Capability = "Restore"
CapabilityExecute Capability = "Execute"
CapabilityConfigure Capability = "Configure"
CapabilityOperate Capability = "Operate"
CapabilityDeploy Capability = "Deploy"
CapabilityIntegrate Capability = "Integrate"
CapabilityGrant Capability = "Grant"
CapabilityRevoke Capability = "Revoke"
CapabilityDelegate Capability = "Delegate"
CapabilityImpersonate Capability = "Impersonate"
CapabilityExport Capability = "Export"
CapabilityImport Capability = "Import"
CapabilityReplicate Capability = "Replicate"
CapabilityEncrypt Capability = "Encrypt"
CapabilityDecrypt Capability = "Decrypt"
CapabilityMask Capability = "Mask"
CapabilityInspect Capability = "Inspect"
CapabilityAudit Capability = "Audit"
CapabilityOverride Capability = "Override"
CapabilityEscalate Capability = "Escalate"
CapabilityBind Capability = "Bind"
CapabilityUse Capability = "Use"
)
// ExposureMode describes how much information becomes visible or extractable.
type ExposureMode string
const (
ExposureModeNone ExposureMode = "None"
ExposureModeMetadata ExposureMode = "Metadata"
ExposureModeMasked ExposureMode = "Masked"
ExposureModeAggregated ExposureMode = "Aggregated"
ExposureModeSynthetic ExposureMode = "Synthetic"
ExposureModePseudonymous ExposureMode = "Pseudonymous"
ExposureModeEncrypted ExposureMode = "Encrypted"
ExposureModePlaintext ExposureMode = "Plaintext"
ExposureModeSecretMaterial ExposureMode = "SecretMaterial"
ExposureModeExportable ExposureMode = "Exportable"
ExposureModeCrossTenantAggregate ExposureMode = "CrossTenantAggregate"
)
// Condition is a CARING runtime or governance condition.
type Condition string
const (
ConditionMFARequired Condition = "MFARequired"
ConditionDeviceTrusted Condition = "DeviceTrusted"
ConditionNetworkTrusted Condition = "NetworkTrusted"
ConditionTicketRequired Condition = "TicketRequired"
ConditionTenantConsentRequired Condition = "TenantConsentRequired"
ConditionCustomerApprovalRequired Condition = "CustomerApprovalRequired"
ConditionDualApprovalRequired Condition = "DualApprovalRequired"
ConditionTimeLimited Condition = "TimeLimited"
ConditionBusinessHoursOnly Condition = "BusinessHoursOnly"
ConditionEmergencyOnly Condition = "EmergencyOnly"
ConditionTrainingRequired Condition = "TrainingRequired"
ConditionContractRequired Condition = "ContractRequired"
ConditionNDARequired Condition = "NDARequired"
ConditionPurposeBound Condition = "PurposeBound"
ConditionCaseBound Condition = "CaseBound"
ConditionEnvironmentBound Condition = "EnvironmentBound"
ConditionNamespaceBound Condition = "NamespaceBound"
ConditionPipelineBound Condition = "PipelineBound"
ConditionChangeWindowBound Condition = "ChangeWindowBound"
ConditionLogged Condition = "Logged"
ConditionRecorded Condition = "Recorded"
ConditionNotificationRequired Condition = "NotificationRequired"
ConditionPostReviewRequired Condition = "PostReviewRequired"
ConditionHumanReviewRequired Condition = "HumanReviewRequired"
ConditionPolicyReviewRequired Condition = "PolicyReviewRequired"
ConditionWorkloadIdentityRequired Condition = "WorkloadIdentityRequired"
)
// LifecycleState describes why access exists now.
type LifecycleState string
const (
LifecycleStateDesign LifecycleState = "Design"
LifecycleStateBuild LifecycleState = "Build"
LifecycleStateTest LifecycleState = "Test"
LifecycleStateReview LifecycleState = "Review"
LifecycleStateRelease LifecycleState = "Release"
LifecycleStateOnboard LifecycleState = "Onboard"
LifecycleStateIntegrate LifecycleState = "Integrate"
LifecycleStateMigrate LifecycleState = "Migrate"
LifecycleStateOperate LifecycleState = "Operate"
LifecycleStateSupport LifecycleState = "Support"
LifecycleStateImprove LifecycleState = "Improve"
LifecycleStateDeprecate LifecycleState = "Deprecate"
LifecycleStateArchive LifecycleState = "Archive"
LifecycleStateIncident LifecycleState = "Incident"
LifecycleStateLegal LifecycleState = "Legal"
LifecycleStateTerminate LifecycleState = "Terminate"
)
// Restriction is an overriding CARING deny or limiting policy effect.
type Restriction string
const (
RestrictionNoAccess Restriction = "NoAccess"
RestrictionSuspended Restriction = "Suspended"
RestrictionTerminated Restriction = "Terminated"
RestrictionQuarantined Restriction = "Quarantined"
RestrictionScopeExcluded Restriction = "ScopeExcluded"
RestrictionDataClassRestricted Restriction = "DataClassRestricted"
RestrictionLegalHold Restriction = "LegalHold"
RestrictionExportBlocked Restriction = "ExportBlocked"
RestrictionImpersonationBlocked Restriction = "ImpersonationBlocked"
RestrictionCrossTenantBlocked Restriction = "CrossTenantBlocked"
RestrictionSecretAccessBlocked Restriction = "SecretAccessBlocked"
RestrictionPolicyFrozen Restriction = "PolicyFrozen"
RestrictionEmergencyLocked Restriction = "EmergencyLocked"
RestrictionRiskDenied Restriction = "RiskDenied"
RestrictionExecutionBlocked Restriction = "ExecutionBlocked"
RestrictionWorkloadCreationBlocked Restriction = "WorkloadCreationBlocked"
RestrictionPrivilegeEscalationBlocked Restriction = "PrivilegeEscalationBlocked"
)
// ExposureEventType is a CARING exceptional or irregular access class.
type ExposureEventType string
const (
ExposureEventSupport ExposureEventType = "X-Support"
ExposureEventBreakGlass ExposureEventType = "X-BreakGlass"
ExposureEventSecurityTest ExposureEventType = "X-SecurityTest"
ExposureEventIncident ExposureEventType = "X-Incident"
ExposureEventLegalDemand ExposureEventType = "X-LegalDemand"
ExposureEventComplianceAudit ExposureEventType = "X-ComplianceAudit"
ExposureEventMigration ExposureEventType = "X-Migration"
ExposureEventRecovery ExposureEventType = "X-Recovery"
ExposureEventAdversarial ExposureEventType = "X-Adversarial"
ExposureEventMisconfiguration ExposureEventType = "X-Misconfiguration"
ExposureEventInducedAccess ExposureEventType = "X-InducedAccess"
ExposureEventPrivilegeEscalation ExposureEventType = "X-PrivilegeEscalation"
)
// AccessPath describes how access is exercised.
type AccessPath string
const (
AccessPathDirect AccessPath = "direct"
AccessPathDelegated AccessPath = "delegated"
AccessPathMediated AccessPath = "mediated"
AccessPathInduced AccessPath = "induced"
)
// CaringScope identifies where a CARING descriptor applies.
type CaringScope struct {
Level ScopeLevel `json:"level" yaml:"level"`
ID string `json:"id" yaml:"id"`
Parent string `json:"parent,omitempty" yaml:"parent,omitempty"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
Resource string `json:"resource,omitempty" yaml:"resource,omitempty"`
Attributes map[string]any `json:"attributes,omitempty" yaml:"attributes,omitempty"`
}
// CaringAccessDescriptor is the executable flex-auth representation of a
// CARING access assignment.
type CaringAccessDescriptor struct {
ID string `json:"id,omitempty" yaml:"id,omitempty"`
Profile string `json:"profile" yaml:"profile"`
SubjectType SubjectType `json:"subject_type" yaml:"subject_type"`
OrganizationRelation OrganizationRelation `json:"organization_relation" yaml:"organization_relation"`
CanonicalRole CanonicalRole `json:"canonical_role" yaml:"canonical_role"`
Scope CaringScope `json:"scope" yaml:"scope"`
Planes []Plane `json:"planes" yaml:"planes"`
Capabilities []Capability `json:"capabilities" yaml:"capabilities"`
ExposureModes []ExposureMode `json:"exposure_modes,omitempty" yaml:"exposure_modes,omitempty"`
Conditions []Condition `json:"conditions,omitempty" yaml:"conditions,omitempty"`
LifecycleState LifecycleState `json:"lifecycle_state,omitempty" yaml:"lifecycle_state,omitempty"`
Restrictions []Restriction `json:"restrictions,omitempty" yaml:"restrictions,omitempty"`
ExposureEvent ExposureEventType `json:"exposure_event,omitempty" yaml:"exposure_event,omitempty"`
DerivedCapabilities []CaringDerivedCapability `json:"derived_capabilities,omitempty" yaml:"derived_capabilities,omitempty"`
AccessPath AccessPath `json:"access_path,omitempty" yaml:"access_path,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// CaringDerivedCapability records effective authority created by another grant.
type CaringDerivedCapability struct {
Capability Capability `json:"capability" yaml:"capability"`
Reason string `json:"reason" yaml:"reason"`
Source string `json:"source,omitempty" yaml:"source,omitempty"`
Planes []Plane `json:"planes,omitempty" yaml:"planes,omitempty"`
ExposureModes []ExposureMode `json:"exposure_modes,omitempty" yaml:"exposure_modes,omitempty"`
}
// CaringConformanceFinding is a diagnostic emitted by descriptive or
// prescriptive CARING validation.
type CaringConformanceFinding struct {
Code string `json:"code" yaml:"code"`
Severity string `json:"severity" yaml:"severity"`
Message string `json:"message" yaml:"message"`
Fields []string `json:"fields,omitempty" yaml:"fields,omitempty"`
Descriptor string `json:"descriptor,omitempty" yaml:"descriptor,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// CaringExposureEvent records exceptional or irregular information exposure.
type CaringExposureEvent struct {
ID string `json:"id" yaml:"id"`
Type ExposureEventType `json:"type" yaml:"type"`
Actor string `json:"actor" yaml:"actor"`
Subject string `json:"subject" yaml:"subject"`
Descriptor *CaringAccessDescriptor `json:"descriptor,omitempty" yaml:"descriptor,omitempty"`
Scope *CaringScope `json:"scope,omitempty" yaml:"scope,omitempty"`
Planes []Plane `json:"planes,omitempty" yaml:"planes,omitempty"`
CapabilitiesUsed []Capability `json:"capabilities_used,omitempty" yaml:"capabilities_used,omitempty"`
DerivedCapabilities []CaringDerivedCapability `json:"derived_capabilities,omitempty" yaml:"derived_capabilities,omitempty"`
ExposureModes []ExposureMode `json:"exposure_modes,omitempty" yaml:"exposure_modes,omitempty"`
Reason string `json:"reason" yaml:"reason"`
AuthoritySource string `json:"authority_source,omitempty" yaml:"authority_source,omitempty"`
Approval string `json:"approval,omitempty" yaml:"approval,omitempty"`
StartTime string `json:"start_time,omitempty" yaml:"start_time,omitempty"`
EndTime string `json:"end_time,omitempty" yaml:"end_time,omitempty"`
ResourcesAccessed []string `json:"resources_accessed,omitempty" yaml:"resources_accessed,omitempty"`
Evidence []string `json:"evidence,omitempty" yaml:"evidence,omitempty"`
NotificationStatus string `json:"notification_status,omitempty" yaml:"notification_status,omitempty"`
PostReview string `json:"post_review,omitempty" yaml:"post_review,omitempty"`
ConformanceFindings []CaringConformanceFinding `json:"conformance_findings,omitempty" yaml:"conformance_findings,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}

View File

@@ -6,23 +6,25 @@ package api
// schemas/resource_manifest.schema.json for the JSON Schema and
// examples/markitect/resource_manifest.yaml for the canonical example.
type ResourceManifest struct {
ID string `json:"id" yaml:"id"`
System string `json:"system" yaml:"system"`
Resources []Resource `json:"resources" yaml:"resources"`
Actions []string `json:"actions,omitempty" yaml:"actions,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
ID string `json:"id" yaml:"id"`
System string `json:"system" yaml:"system"`
Resources []Resource `json:"resources" yaml:"resources"`
Actions []string `json:"actions,omitempty" yaml:"actions,omitempty"`
CaringProfile string `json:"caring_profile,omitempty" yaml:"caring_profile,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// Resource is one entry in a ResourceManifest.
type Resource struct {
ID string `json:"id" yaml:"id"`
Type string `json:"type" yaml:"type"`
Path string `json:"path,omitempty" yaml:"path,omitempty"`
Parent string `json:"parent,omitempty" yaml:"parent,omitempty"`
Labels []string `json:"labels,omitempty" yaml:"labels,omitempty"`
TrustZone string `json:"trust_zone,omitempty" yaml:"trust_zone,omitempty"`
Owner string `json:"owner,omitempty" yaml:"owner,omitempty"`
Attributes map[string]any `json:"attributes,omitempty" yaml:"attributes,omitempty"`
ID string `json:"id" yaml:"id"`
Type string `json:"type" yaml:"type"`
Path string `json:"path,omitempty" yaml:"path,omitempty"`
Parent string `json:"parent,omitempty" yaml:"parent,omitempty"`
Labels []string `json:"labels,omitempty" yaml:"labels,omitempty"`
TrustZone string `json:"trust_zone,omitempty" yaml:"trust_zone,omitempty"`
Owner string `json:"owner,omitempty" yaml:"owner,omitempty"`
Caring *CaringAccessDescriptor `json:"caring,omitempty" yaml:"caring,omitempty"`
Attributes map[string]any `json:"attributes,omitempty" yaml:"attributes,omitempty"`
}
// FlexAuthContractV0 is the metadata.flex_auth_contract value that

View File

@@ -3,9 +3,13 @@
JSON Schema definitions for flex-auth's canonical artefacts:
- `resource_manifest.schema.json` (pinned in `FLEX-WP-0005 P5.3`)
- `protected_system_manifest.schema.json`
- `subject_manifest.schema.json`
- `relationship_fact.schema.json`
- `policy_package_frontmatter.schema.json`
- `caring_profile.schema.json`
- `caring_access_descriptor.schema.json`
- `policy_package.schema.json`
- `policy_fixture.schema.json`
- `check_request.schema.json`
- `decision_envelope.schema.json`
- `audit_event.schema.json`

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://flex-auth.netkingdom/schemas/audit_event.schema.json",
"title": "AuditEvent",
"type": "object",
"additionalProperties": false,
"required": ["id", "type", "subject"],
"properties": {
"id": {"type": "string", "minLength": 1},
"type": {"type": "string", "minLength": 1},
"decision_id": {"type": "string", "minLength": 1},
"subject": {"$ref": "https://flex-auth.netkingdom/schemas/check_request.schema.json#/$defs/subject_ref"},
"resource": {"$ref": "https://flex-auth.netkingdom/schemas/check_request.schema.json#/$defs/resource_ref"},
"action": {"type": "string", "minLength": 1},
"effect": {"enum": ["allow", "deny", "redact", "audit_only", "not_applicable"]},
"timestamp": {"type": "string", "minLength": 1},
"exposure_event": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/exposure_event"},
"metadata": {"type": "object", "additionalProperties": true}
}
}

View File

@@ -0,0 +1,358 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json",
"title": "CaringAccessDescriptor",
"description": "Executable flex-auth representation of a CARING access descriptor pinned to CARING 0.4.0-RC2.",
"type": "object",
"additionalProperties": false,
"required": [
"profile",
"subject_type",
"organization_relation",
"canonical_role",
"scope",
"planes",
"capabilities"
],
"properties": {
"id": {"type": "string", "minLength": 1},
"profile": {"const": "caring-0.4.0-rc2"},
"subject_type": {"$ref": "#/$defs/subject_type"},
"organization_relation": {"$ref": "#/$defs/organization_relation"},
"canonical_role": {"$ref": "#/$defs/canonical_role"},
"scope": {"$ref": "#/$defs/scope"},
"planes": {
"type": "array",
"items": {"$ref": "#/$defs/plane"},
"minItems": 1,
"uniqueItems": true
},
"capabilities": {
"type": "array",
"items": {"$ref": "#/$defs/capability"},
"minItems": 1,
"uniqueItems": true
},
"exposure_modes": {
"type": "array",
"items": {"$ref": "#/$defs/exposure_mode"},
"uniqueItems": true
},
"conditions": {
"type": "array",
"items": {"$ref": "#/$defs/condition"},
"uniqueItems": true
},
"lifecycle_state": {"$ref": "#/$defs/lifecycle_state"},
"restrictions": {
"type": "array",
"items": {"$ref": "#/$defs/restriction"},
"uniqueItems": true
},
"exposure_event": {"$ref": "#/$defs/exposure_event_type"},
"derived_capabilities": {
"type": "array",
"items": {"$ref": "#/$defs/derived_capability"}
},
"access_path": {"enum": ["direct", "delegated", "mediated", "induced"]},
"metadata": {"type": "object", "additionalProperties": true}
},
"$defs": {
"subject_type": {
"enum": [
"Human",
"Group",
"Organization",
"Service",
"Automation",
"Agent",
"System",
"Device",
"Process",
"Anonymous",
"Unknown"
]
},
"organization_relation": {
"enum": [
"Vendor",
"ServiceProvider",
"Distributor",
"Consultant",
"Customer",
"Community",
"Authority",
"Unknown"
]
},
"canonical_role": {
"enum": [
"Creator",
"Builder",
"Verifier",
"Maintainer",
"Integrator",
"Operator",
"Manager",
"Coach",
"Doer"
]
},
"scope_level": {
"enum": [
"Ecosystem",
"Product",
"Platform",
"Cluster",
"Environment",
"Tenant",
"Namespace",
"Domain",
"Workspace",
"Project",
"Process",
"Dataset",
"Resource",
"Subresource",
"Record",
"Field",
"Action"
]
},
"scope": {
"type": "object",
"additionalProperties": false,
"required": ["level", "id"],
"properties": {
"level": {"$ref": "#/$defs/scope_level"},
"id": {"type": "string", "minLength": 1},
"parent": {"type": "string", "minLength": 1},
"tenant": {"type": "string", "minLength": 1},
"resource": {"type": "string", "minLength": 1},
"attributes": {"type": "object", "additionalProperties": true}
}
},
"plane": {
"enum": [
"Intent",
"Build",
"Runtime",
"Execution",
"Configuration",
"Data",
"Identity",
"Policy",
"Secret",
"Audit",
"Commercial",
"Community"
]
},
"capability": {
"enum": [
"View",
"ViewCollection",
"Observe",
"Create",
"EditOwn",
"EditAssigned",
"EditAny",
"DeleteOwn",
"DeleteAny",
"BulkDelete",
"Submit",
"Comment",
"Review",
"Approve",
"Reject",
"Publish",
"Archive",
"Restore",
"Execute",
"Configure",
"Operate",
"Deploy",
"Integrate",
"Grant",
"Revoke",
"Delegate",
"Impersonate",
"Export",
"Import",
"Replicate",
"Encrypt",
"Decrypt",
"Mask",
"Inspect",
"Audit",
"Override",
"Escalate",
"Bind",
"Use"
]
},
"exposure_mode": {
"enum": [
"None",
"Metadata",
"Masked",
"Aggregated",
"Synthetic",
"Pseudonymous",
"Encrypted",
"Plaintext",
"SecretMaterial",
"Exportable",
"CrossTenantAggregate"
]
},
"condition": {
"enum": [
"MFARequired",
"DeviceTrusted",
"NetworkTrusted",
"TicketRequired",
"TenantConsentRequired",
"CustomerApprovalRequired",
"DualApprovalRequired",
"TimeLimited",
"BusinessHoursOnly",
"EmergencyOnly",
"TrainingRequired",
"ContractRequired",
"NDARequired",
"PurposeBound",
"CaseBound",
"EnvironmentBound",
"NamespaceBound",
"PipelineBound",
"ChangeWindowBound",
"Logged",
"Recorded",
"NotificationRequired",
"PostReviewRequired",
"HumanReviewRequired",
"PolicyReviewRequired",
"WorkloadIdentityRequired"
]
},
"lifecycle_state": {
"enum": [
"Design",
"Build",
"Test",
"Review",
"Release",
"Onboard",
"Integrate",
"Migrate",
"Operate",
"Support",
"Improve",
"Deprecate",
"Archive",
"Incident",
"Legal",
"Terminate"
]
},
"restriction": {
"enum": [
"NoAccess",
"Suspended",
"Terminated",
"Quarantined",
"ScopeExcluded",
"DataClassRestricted",
"LegalHold",
"ExportBlocked",
"ImpersonationBlocked",
"CrossTenantBlocked",
"SecretAccessBlocked",
"PolicyFrozen",
"EmergencyLocked",
"RiskDenied",
"ExecutionBlocked",
"WorkloadCreationBlocked",
"PrivilegeEscalationBlocked"
]
},
"exposure_event_type": {
"enum": [
"X-Support",
"X-BreakGlass",
"X-SecurityTest",
"X-Incident",
"X-LegalDemand",
"X-ComplianceAudit",
"X-Migration",
"X-Recovery",
"X-Adversarial",
"X-Misconfiguration",
"X-InducedAccess",
"X-PrivilegeEscalation"
]
},
"derived_capability": {
"type": "object",
"additionalProperties": false,
"required": ["capability", "reason"],
"properties": {
"capability": {"$ref": "#/$defs/capability"},
"reason": {"type": "string", "minLength": 1},
"source": {"type": "string", "minLength": 1},
"planes": {
"type": "array",
"items": {"$ref": "#/$defs/plane"},
"uniqueItems": true
},
"exposure_modes": {
"type": "array",
"items": {"$ref": "#/$defs/exposure_mode"},
"uniqueItems": true
}
}
},
"conformance_finding": {
"type": "object",
"additionalProperties": false,
"required": ["code", "severity", "message"],
"properties": {
"code": {"type": "string", "minLength": 1},
"severity": {"enum": ["info", "warning", "violation", "blocked"]},
"message": {"type": "string", "minLength": 1},
"fields": {"type": "array", "items": {"type": "string", "minLength": 1}},
"descriptor": {"type": "string", "minLength": 1},
"metadata": {"type": "object", "additionalProperties": true}
}
},
"exposure_event": {
"type": "object",
"additionalProperties": false,
"required": ["id", "type", "actor", "subject", "reason"],
"properties": {
"id": {"type": "string", "minLength": 1},
"type": {"$ref": "#/$defs/exposure_event_type"},
"actor": {"type": "string", "minLength": 1},
"subject": {"type": "string", "minLength": 1},
"descriptor": {"$ref": "#"},
"scope": {"$ref": "#/$defs/scope"},
"planes": {"type": "array", "items": {"$ref": "#/$defs/plane"}},
"capabilities_used": {"type": "array", "items": {"$ref": "#/$defs/capability"}},
"derived_capabilities": {"type": "array", "items": {"$ref": "#/$defs/derived_capability"}},
"exposure_modes": {"type": "array", "items": {"$ref": "#/$defs/exposure_mode"}},
"reason": {"type": "string", "minLength": 1},
"authority_source": {"type": "string", "minLength": 1},
"approval": {"type": "string", "minLength": 1},
"start_time": {"type": "string", "minLength": 1},
"end_time": {"type": "string", "minLength": 1},
"resources_accessed": {"type": "array", "items": {"type": "string", "minLength": 1}},
"evidence": {"type": "array", "items": {"type": "string", "minLength": 1}},
"notification_status": {"type": "string", "minLength": 1},
"post_review": {"type": "string", "minLength": 1},
"conformance_findings": {"type": "array", "items": {"$ref": "#/$defs/conformance_finding"}},
"metadata": {"type": "object", "additionalProperties": true}
}
}
}
}

View File

@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://flex-auth.netkingdom/schemas/caring_profile.schema.json",
"title": "CaringProfile",
"description": "Machine-readable pin for a CARING profile supported by flex-auth.",
"type": "object",
"additionalProperties": false,
"required": ["id", "standard", "version"],
"properties": {
"id": {"const": "caring-0.4.0-rc2"},
"standard": {"const": "CARING"},
"version": {"const": "0.4.0-RC2"},
"source": {"type": "string", "minLength": 1},
"metadata": {"type": "object", "additionalProperties": true}
}
}

View File

@@ -0,0 +1,42 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://flex-auth.netkingdom/schemas/check_request.schema.json",
"title": "CheckRequest",
"type": "object",
"additionalProperties": false,
"required": ["subject", "action", "resource"],
"properties": {
"id": {"type": "string", "minLength": 1},
"subject": {"$ref": "#/$defs/subject_ref"},
"action": {"type": "string", "minLength": 1},
"resource": {"$ref": "#/$defs/resource_ref"},
"context": {"type": "object", "additionalProperties": true},
"caring_context": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json"},
"policy_version": {"type": "string", "minLength": 1}
},
"$defs": {
"subject_ref": {
"type": "object",
"additionalProperties": false,
"required": ["id"],
"properties": {
"id": {"type": "string", "minLength": 1},
"type": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/subject_type"},
"tenant": {"type": "string", "minLength": 1},
"attributes": {"type": "object", "additionalProperties": true}
}
},
"resource_ref": {
"type": "object",
"additionalProperties": false,
"required": ["id"],
"properties": {
"id": {"type": "string", "minLength": 1},
"type": {"type": "string", "minLength": 1},
"system": {"type": "string", "minLength": 1},
"tenant": {"type": "string", "minLength": 1},
"attributes": {"type": "object", "additionalProperties": true}
}
}
}
}

View File

@@ -0,0 +1,74 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://flex-auth.netkingdom/schemas/decision_envelope.schema.json",
"title": "DecisionEnvelope",
"type": "object",
"additionalProperties": false,
"required": ["id", "effect", "resource", "subject", "provenance"],
"properties": {
"id": {"type": "string", "minLength": 1},
"request_id": {"type": "string", "minLength": 1},
"effect": {"enum": ["allow", "deny", "redact", "audit_only", "not_applicable"]},
"reason": {"type": "string"},
"matched_policy_version": {"type": "string", "minLength": 1},
"matched_rule": {"type": "string", "minLength": 1},
"resource": {"$ref": "https://flex-auth.netkingdom/schemas/check_request.schema.json#/$defs/resource_ref"},
"subject": {"$ref": "https://flex-auth.netkingdom/schemas/check_request.schema.json#/$defs/subject_ref"},
"obligations": {"type": "array", "items": {"$ref": "#/$defs/obligation"}},
"diagnostics": {"type": "object", "additionalProperties": true},
"provenance": {"$ref": "#/$defs/provenance"},
"caring": {"$ref": "#/$defs/caring_decision_metadata"}
},
"$defs": {
"obligation": {
"type": "object",
"additionalProperties": false,
"required": ["type"],
"properties": {
"type": {"type": "string", "minLength": 1},
"parameters": {"type": "object", "additionalProperties": true}
}
},
"provenance": {
"type": "object",
"additionalProperties": false,
"required": ["evaluator", "mode"],
"properties": {
"evaluator": {"type": "string", "minLength": 1},
"mode": {"type": "string", "minLength": 1},
"policy_package": {"type": "string", "minLength": 1},
"policy_version": {"type": "string", "minLength": 1},
"directory_etag": {"type": "string", "minLength": 1},
"decision_time": {"type": "string", "minLength": 1}
}
},
"caring_decision_metadata": {
"type": "object",
"additionalProperties": false,
"required": ["profile"],
"properties": {
"profile": {"const": "caring-0.4.0-rc2"},
"descriptor": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json"},
"restrictions_evaluated": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/restriction"},
"uniqueItems": true
},
"exposure_modes": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/exposure_mode"},
"uniqueItems": true
},
"derived_capabilities": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/derived_capability"}
},
"conformance_findings": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/conformance_finding"}
},
"exposure_event": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/exposure_event"}
}
}
}
}

View File

@@ -0,0 +1,33 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://flex-auth.netkingdom/schemas/policy_fixture.schema.json",
"title": "PolicyFixture",
"type": "object",
"additionalProperties": false,
"required": ["id", "request", "expect"],
"properties": {
"id": {"type": "string", "minLength": 1},
"request": {"$ref": "https://flex-auth.netkingdom/schemas/check_request.schema.json"},
"expect": {"$ref": "#/$defs/decision_expectation"},
"metadata": {"type": "object", "additionalProperties": true}
},
"$defs": {
"decision_expectation": {
"type": "object",
"additionalProperties": false,
"required": ["effect"],
"properties": {
"effect": {"enum": ["allow", "deny", "redact", "audit_only", "not_applicable"]},
"reason": {"type": "string", "minLength": 1},
"obligations": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/decision_envelope.schema.json#/$defs/obligation"}
},
"conformance_findings": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/conformance_finding"}
}
}
}
}
}

View File

@@ -0,0 +1,68 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://flex-auth.netkingdom/schemas/policy_package.schema.json",
"title": "PolicyPackageMetadata",
"type": "object",
"additionalProperties": false,
"required": ["id", "version", "package", "caring"],
"properties": {
"id": {"type": "string", "minLength": 1},
"name": {"type": "string", "minLength": 1},
"version": {"type": "string", "minLength": 1},
"status": {"type": "string", "minLength": 1},
"package": {"type": "string", "minLength": 1},
"caring": {"$ref": "#/$defs/caring_policy_metadata"},
"activation": {"type": "object", "additionalProperties": true},
"metadata": {"type": "object", "additionalProperties": true}
},
"$defs": {
"caring_policy_metadata": {
"type": "object",
"additionalProperties": false,
"required": ["profile"],
"properties": {
"profile": {"const": "caring-0.4.0-rc2"},
"canonical_roles": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/canonical_role"},
"uniqueItems": true
},
"organization_relations": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/organization_relation"},
"uniqueItems": true
},
"scopes": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/scope"}
},
"planes": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/plane"},
"uniqueItems": true
},
"capabilities": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/capability"},
"uniqueItems": true
},
"exposure_modes": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/exposure_mode"},
"uniqueItems": true
},
"conditions": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/condition"},
"uniqueItems": true
},
"restrictions": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/restriction"},
"uniqueItems": true
},
"metadata": {"type": "object", "additionalProperties": true}
}
}
}
}

View File

@@ -0,0 +1,69 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://flex-auth.netkingdom/schemas/protected_system_manifest.schema.json",
"title": "ProtectedSystemManifest",
"type": "object",
"additionalProperties": false,
"required": ["id"],
"properties": {
"id": {"type": "string", "minLength": 1},
"name": {"type": "string", "minLength": 1},
"description": {"type": "string"},
"resource_types": {
"type": "array",
"items": {"$ref": "#/$defs/resource_type"}
},
"actions": {
"type": "array",
"items": {"$ref": "#/$defs/action"}
},
"caring_profiles": {
"type": "array",
"items": {"const": "caring-0.4.0-rc2"},
"uniqueItems": true
},
"metadata": {"type": "object", "additionalProperties": true}
},
"$defs": {
"resource_type": {
"type": "object",
"additionalProperties": false,
"required": ["name"],
"properties": {
"name": {"type": "string", "minLength": 1},
"parent_types": {"type": "array", "items": {"type": "string", "minLength": 1}},
"scope_level": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/scope_level"},
"planes": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/plane"},
"uniqueItems": true
},
"metadata": {"type": "object", "additionalProperties": true}
}
},
"action": {
"type": "object",
"additionalProperties": false,
"required": ["name"],
"properties": {
"name": {"type": "string", "minLength": 1},
"capabilities": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/capability"},
"uniqueItems": true
},
"planes": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/plane"},
"uniqueItems": true
},
"exposure_modes": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/exposure_mode"},
"uniqueItems": true
},
"metadata": {"type": "object", "additionalProperties": true}
}
}
}
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://flex-auth.netkingdom/schemas/relationship_fact.schema.json",
"title": "RelationshipFact",
"type": "object",
"additionalProperties": false,
"required": ["id", "subject", "relation", "object"],
"properties": {
"id": {"type": "string", "minLength": 1},
"system": {"type": "string", "minLength": 1},
"subject": {"type": "string", "minLength": 1},
"relation": {"type": "string", "minLength": 1},
"object": {"type": "string", "minLength": 1},
"tenant": {"type": "string", "minLength": 1},
"conditions": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/condition"},
"uniqueItems": true
},
"caring": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json"},
"provenance": {"type": "object", "additionalProperties": true},
"metadata": {"type": "object", "additionalProperties": true}
}
}

View File

@@ -28,6 +28,11 @@
"items": {"type": "string", "minLength": 1},
"uniqueItems": true
},
"caring_profile": {
"type": "string",
"description": "Optional CARING profile identifier used by resource-level descriptors.",
"const": "caring-0.4.0-rc2"
},
"metadata": {
"type": "object",
"description": "Free-form provenance and contract metadata. Conventions: 'source' (origin description), 'flex_auth_contract' (contract version string, currently 'resource-registration-v0').",
@@ -76,6 +81,10 @@
"description": "Owner identifier, conventionally 'team:<slug>' or 'user:<slug>'.",
"minLength": 1
},
"caring": {
"description": "Optional CARING descriptor for this resource. Policy packages may require this field for conformance checks.",
"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json"
},
"attributes": {
"type": "object",
"description": "Free-form attributes that policy packages may consult. Reserved keys may be defined by individual policy packages.",

View File

@@ -0,0 +1,68 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://flex-auth.netkingdom/schemas/subject_manifest.schema.json",
"title": "SubjectManifest",
"type": "object",
"additionalProperties": false,
"required": ["id"],
"properties": {
"id": {"type": "string", "minLength": 1},
"subjects": {"type": "array", "items": {"$ref": "#/$defs/subject"}},
"groups": {"type": "array", "items": {"$ref": "#/$defs/group"}},
"teams": {"type": "array", "items": {"$ref": "#/$defs/group"}},
"tenants": {"type": "array", "items": {"$ref": "#/$defs/tenant"}},
"metadata": {"type": "object", "additionalProperties": true}
},
"$defs": {
"subject": {
"type": "object",
"additionalProperties": false,
"required": ["id", "type"],
"properties": {
"id": {"type": "string", "minLength": 1},
"type": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/subject_type"},
"display_name": {"type": "string", "minLength": 1},
"organization_relation": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/organization_relation"},
"roles": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/canonical_role"},
"uniqueItems": true
},
"groups": {"type": "array", "items": {"type": "string", "minLength": 1}},
"tenant": {"type": "string", "minLength": 1},
"claims": {"type": "object", "additionalProperties": true},
"caring_descriptors": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json"}
},
"metadata": {"type": "object", "additionalProperties": true}
}
},
"group": {
"type": "object",
"additionalProperties": false,
"required": ["id"],
"properties": {
"id": {"type": "string", "minLength": 1},
"display_name": {"type": "string", "minLength": 1},
"members": {"type": "array", "items": {"type": "string", "minLength": 1}},
"tenant": {"type": "string", "minLength": 1},
"caring_descriptors": {
"type": "array",
"items": {"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json"}
},
"metadata": {"type": "object", "additionalProperties": true}
}
},
"tenant": {
"type": "object",
"additionalProperties": false,
"required": ["id"],
"properties": {
"id": {"type": "string", "minLength": 1},
"name": {"type": "string", "minLength": 1},
"metadata": {"type": "object", "additionalProperties": true}
}
}
}
}

View File

@@ -67,7 +67,7 @@ Backends may change later, but these envelopes must stay stable:
```task
id: FLEX-WP-0002-T001
status: todo
status: done
priority: high
state_hub_task_id: "534e5251-8529-48fe-8cf8-b3b6bc4ec1f4"
```