Add CARING examples and coverage
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:05:18 +02:00
parent 49655e40e0
commit 18054bd160
10 changed files with 318 additions and 1 deletions

View File

@@ -6,3 +6,7 @@ Small fixtures for the executable CARING 0.4.0-RC2 profile used by
These are intentionally compact. They prove that the canonical descriptor,
request, decision, registry, audit, and Rego-in-Markdown policy package
shapes can round-trip through `pkg/api` and `internal/policy`.
The set includes local subjects, groups, teams, project resources, inherited
relationship facts, exposure events, allow/deny fixtures, and a
redact-with-obligation policy package.

View File

@@ -0,0 +1,20 @@
{
"id": "exposure:tenant-alpha-support-001",
"type": "X-Support",
"actor": "user:alice",
"subject": "user:bob",
"scope": {
"level": "Resource",
"id": "document:alpha-plan",
"tenant": "tenant:alpha",
"resource": "document:alpha-plan"
},
"planes": ["Data"],
"exposure_modes": ["Masked"],
"reason": "Support review of masked project plan",
"decision_id": "decision:tenant-alpha-support-001",
"timestamp": "2026-05-17T00:00:00Z",
"metadata": {
"source": "examples/caring/exposure_event.json"
}
}

View File

@@ -0,0 +1,38 @@
- id: rel:reviewers-project-reviewer
system: markitect-tool
subject: team:project-reviewers
relation: reviewer
object: project:alpha-redesign
tenant: tenant:alpha
conditions:
- Logged
caring:
id: descriptor:tenant-alpha-project-reviewer
profile: caring-0.4.0-rc2
subject_type: Group
organization_relation: Customer
canonical_role: Verifier
scope:
level: Project
id: project:alpha-redesign
tenant: tenant:alpha
resource: project:alpha-redesign
planes:
- Data
capabilities:
- Review
exposure_modes:
- Masked
conditions:
- Logged
restrictions:
- ExportBlocked
- id: rel:alpha-plan-inherits-project-reviewer
system: markitect-tool
subject: document:alpha-plan
relation: inherits
object: project:alpha-redesign
tenant: tenant:alpha
metadata:
inheritance: parent
source: examples/caring/inherited_relationships.yaml

View File

@@ -0,0 +1,27 @@
id: markitect-project-resources
system: markitect-tool
resources:
- id: project:alpha-redesign
type: project
path: /projects/alpha-redesign
labels:
- project
trust_zone: internal
owner: team:project-reviewers
- id: document:alpha-plan
type: document
path: /projects/alpha-redesign/plan
parent: project:alpha-redesign
labels:
- internal
- pii
trust_zone: internal
owner: team:project-reviewers
actions:
- read
- review
- export
caring_profile: caring-0.4.0-rc2
metadata:
flex_auth_contract: resource-registration-v0
source: examples/caring/project_resource_manifest.yaml

View File

@@ -0,0 +1,132 @@
---
id: markitect.documents.mask-pii
name: Markitect masked PII read
namespace: markitect:document
version: v1
status: draft
package: flexauth.markitect.redact
actions:
- read
owner: team:project-reviewers
caring:
profile: caring-0.4.0-rc2
enforce: false
canonical_roles:
- Verifier
organization_relations:
- Customer
scopes:
- level: Resource
id: document:alpha-plan
tenant: tenant:alpha
planes:
- Data
capabilities:
- View
- Mask
exposure_modes:
- Masked
conditions:
- Logged
restrictions:
- ExportBlocked
metadata:
source: examples/caring/redact_policy_package.md
---
# Markitect Masked PII Read
This package returns a redaction decision when a verifier may inspect a
document only through masked fields.
## Rules
```rego
import future.keywords.if
import future.keywords.in
default decision := {"effect": "deny", "reason": "no_matching_rule"}
decision := {
"effect": "redact",
"reason": "masked_pii",
"obligations": [{
"type": "mask_fields",
"parameters": {"fields": ["email", "phone"]}
}]
} if {
input.action == "read"
input.resource.id == "document:alpha-plan"
"Mask" in input.caring_context.capabilities
"Masked" in input.caring_context.exposure_modes
}
```
## Tests
```rego test
package flexauth.markitect.redact_test
import future.keywords.if
import data.flexauth.markitect.redact
test_masked_reader_gets_redaction if {
redact.decision.effect == "redact" with input as {
"action": "read",
"resource": {"id": "document:alpha-plan"},
"caring_context": {
"capabilities": ["View", "Mask"],
"exposure_modes": ["Masked"]
}
}
}
```
## Fixtures
```yaml fixture
id: fixture:masked-pii-redact
request:
id: check:masked-pii
subject:
id: user:bob
type: Human
tenant: tenant:alpha
action: read
resource:
id: document:alpha-plan
type: document
system: markitect-tool
tenant: tenant:alpha
caring_context:
id: descriptor:tenant-alpha-masked-pii-reviewer
profile: caring-0.4.0-rc2
subject_type: Human
organization_relation: Customer
canonical_role: Verifier
scope:
level: Resource
id: document:alpha-plan
tenant: tenant:alpha
resource: document:alpha-plan
planes:
- Data
capabilities:
- View
- Mask
exposure_modes:
- Masked
conditions:
- Logged
restrictions:
- ExportBlocked
expect:
effect: redact
reason: masked_pii
obligations:
- type: mask_fields
parameters:
fields:
- email
- phone
```

View File

@@ -0,0 +1,37 @@
id: tenant-alpha-project-team
tenants:
- id: tenant:alpha
name: Tenant Alpha
subjects:
- id: user:alice
type: Human
display_name: Alice Example
organization_relation: Customer
roles:
- Doer
groups:
- group:platform-architecture
tenant: tenant:alpha
- id: user:bob
type: Human
display_name: Bob Example
organization_relation: Customer
roles:
- Verifier
groups:
- team:project-reviewers
tenant: tenant:alpha
groups:
- id: group:platform-architecture
display_name: Platform Architecture
members:
- user:alice
tenant: tenant:alpha
teams:
- id: team:project-reviewers
display_name: Project Reviewers
members:
- user:bob
tenant: tenant:alpha
metadata:
source: examples/caring/team_subject_manifest.yaml

View File

@@ -50,6 +50,30 @@ func TestLoadPolicyPackageMarkdownValidates(t *testing.T) {
}
}
func TestRedactPolicyPackageMarkdownValidates(t *testing.T) {
pkg, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "caring", "redact_policy_package.md"))
if err != nil {
t.Fatalf("LoadAndValidateFile: %v", err)
}
if !pkg.Valid {
t.Fatalf("pkg.Valid = false\n%s", formatValidation(pkg.Validation))
}
if len(pkg.Validation.Fixtures) != 1 {
t.Fatalf("Validation.Fixtures len = %d; want 1", len(pkg.Validation.Fixtures))
}
fixture := pkg.Validation.Fixtures[0]
if !fixture.Passed {
t.Fatalf("fixture failed: %s\nactual: %+v", fixture.Error, fixture.Actual)
}
if fixture.Actual.Effect != api.DecisionEffectRedact {
t.Fatalf("fixture.Actual.Effect = %q; want redact", fixture.Actual.Effect)
}
if len(fixture.Actual.Obligations) != 1 || fixture.Actual.Obligations[0].Type != "mask_fields" {
t.Fatalf("fixture.Actual.Obligations = %+v; want mask_fields", fixture.Actual.Obligations)
}
}
func TestCaringFindingsAreAdvisoryUntilEnforced(t *testing.T) {
doc := inlinePolicy(false, "allow")
pkg, err := policy.Load([]byte(doc), "inline-policy.md")

View File

@@ -30,6 +30,32 @@ func TestRegistryManifestsParse(t *testing.T) {
}
}
func TestExpandedRegistryExamplesParse(t *testing.T) {
var subjects api.SubjectManifest
loadYAML(t, filepath.Join("..", "..", "examples", "caring", "team_subject_manifest.yaml"), &subjects)
if len(subjects.Teams) != 1 || subjects.Teams[0].ID != "team:project-reviewers" {
t.Fatalf("subjects.Teams = %+v; want team:project-reviewers", subjects.Teams)
}
var resources api.ResourceManifest
loadYAML(t, filepath.Join("..", "..", "examples", "caring", "project_resource_manifest.yaml"), &resources)
if len(resources.Resources) != 2 || resources.Resources[1].Parent != "project:alpha-redesign" {
t.Fatalf("resources = %+v; want document inheriting from project", resources.Resources)
}
var relationships []api.RelationshipFact
loadYAML(t, filepath.Join("..", "..", "examples", "caring", "inherited_relationships.yaml"), &relationships)
if len(relationships) != 2 {
t.Fatalf("relationships len = %d; want 2", len(relationships))
}
if relationships[0].Caring == nil || relationships[0].Caring.CanonicalRole != api.CanonicalRoleVerifier {
t.Fatalf("relationships[0].Caring = %+v; want Verifier descriptor", relationships[0].Caring)
}
if relationships[1].Relation != "inherits" {
t.Fatalf("relationships[1].Relation = %q; want inherits", relationships[1].Relation)
}
}
func loadYAML(t *testing.T, path string, out any) {
t.Helper()

View File

@@ -82,6 +82,15 @@ func TestDecisionAndAuditExamplesParse(t *testing.T) {
if audit.DecisionID != decision.ID {
t.Errorf("Audit.DecisionID = %q; want %q", audit.DecisionID, decision.ID)
}
var exposure api.CaringExposureEvent
loadJSON(t, filepath.Join("..", "..", "examples", "caring", "exposure_event.json"), &exposure)
if exposure.Type != api.ExposureEventSupport {
t.Errorf("Exposure.Type = %q; want X-Support", exposure.Type)
}
if len(exposure.ExposureModes) != 1 || exposure.ExposureModes[0] != api.ExposureModeMasked {
t.Errorf("Exposure.ExposureModes = %v; want [Masked]", exposure.ExposureModes)
}
}
func TestSchemaFilesAreJSON(t *testing.T) {

View File

@@ -230,7 +230,7 @@ Add a minimal service skeleton only after CLI/library behavior is stable.
```task
id: FLEX-WP-0002-T008
status: todo
status: done
priority: high
state_hub_task_id: "6cbe572a-2877-4936-8ef3-63b79900fae2"
```