From 18054bd1600b8bdab434d8fcb443a67f68ae3fd7 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 17 May 2026 06:05:18 +0200 Subject: [PATCH] Add CARING examples and coverage --- examples/caring/README.md | 4 + examples/caring/exposure_event.json | 20 +++ examples/caring/inherited_relationships.yaml | 38 +++++ .../caring/project_resource_manifest.yaml | 27 ++++ examples/caring/redact_policy_package.md | 132 ++++++++++++++++++ examples/caring/team_subject_manifest.yaml | 37 +++++ internal/policy/package_test.go | 24 ++++ internal/registry/manifest_test.go | 26 ++++ pkg/api/canonical_test.go | 9 ++ ...-WP-0002-standalone-policy-as-code-core.md | 2 +- 10 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 examples/caring/exposure_event.json create mode 100644 examples/caring/inherited_relationships.yaml create mode 100644 examples/caring/project_resource_manifest.yaml create mode 100644 examples/caring/redact_policy_package.md create mode 100644 examples/caring/team_subject_manifest.yaml diff --git a/examples/caring/README.md b/examples/caring/README.md index 31b1532..fc6b760 100644 --- a/examples/caring/README.md +++ b/examples/caring/README.md @@ -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. diff --git a/examples/caring/exposure_event.json b/examples/caring/exposure_event.json new file mode 100644 index 0000000..f70df2b --- /dev/null +++ b/examples/caring/exposure_event.json @@ -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" + } +} diff --git a/examples/caring/inherited_relationships.yaml b/examples/caring/inherited_relationships.yaml new file mode 100644 index 0000000..149df7c --- /dev/null +++ b/examples/caring/inherited_relationships.yaml @@ -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 diff --git a/examples/caring/project_resource_manifest.yaml b/examples/caring/project_resource_manifest.yaml new file mode 100644 index 0000000..66a3fa5 --- /dev/null +++ b/examples/caring/project_resource_manifest.yaml @@ -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 diff --git a/examples/caring/redact_policy_package.md b/examples/caring/redact_policy_package.md new file mode 100644 index 0000000..ad82faf --- /dev/null +++ b/examples/caring/redact_policy_package.md @@ -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 +``` diff --git a/examples/caring/team_subject_manifest.yaml b/examples/caring/team_subject_manifest.yaml new file mode 100644 index 0000000..bb7f24d --- /dev/null +++ b/examples/caring/team_subject_manifest.yaml @@ -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 diff --git a/internal/policy/package_test.go b/internal/policy/package_test.go index 6679e99..8765f46 100644 --- a/internal/policy/package_test.go +++ b/internal/policy/package_test.go @@ -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") diff --git a/internal/registry/manifest_test.go b/internal/registry/manifest_test.go index cc4ed86..f6667f3 100644 --- a/internal/registry/manifest_test.go +++ b/internal/registry/manifest_test.go @@ -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() diff --git a/pkg/api/canonical_test.go b/pkg/api/canonical_test.go index 193c87b..03b0e4c 100644 --- a/pkg/api/canonical_test.go +++ b/pkg/api/canonical_test.go @@ -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) { diff --git a/workplans/FLEX-WP-0002-standalone-policy-as-code-core.md b/workplans/FLEX-WP-0002-standalone-policy-as-code-core.md index 35af8f8..c2bfbef 100644 --- a/workplans/FLEX-WP-0002-standalone-policy-as-code-core.md +++ b/workplans/FLEX-WP-0002-standalone-policy-as-code-core.md @@ -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" ```