Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ An open-source Go module and verification CLI. Four operations:
## Quick Start

```bash
go install github.com/Clyra-AI/proof/cmd/proof@v0.2.0
PROOF_VERSION="$(gh release view --repo Clyra-AI/proof --json tagName -q .tagName 2>/dev/null || curl -fsSL https://api.github.com/repos/Clyra-AI/proof/releases/latest | python3 -c 'import json,sys; print(json.load(sys.stdin)[\"tag_name\"])')"
go install github.com/Clyra-AI/proof/cmd/proof@"${PROOF_VERSION}"

proof types list # 15 built-in record types
proof frameworks list # 8 compliance framework definitions
Expand Down Expand Up @@ -180,7 +181,16 @@ All digests carry `algo_id` (sha256 or hmac-sha256) and optional `salt_id` metad

## Compliance Framework Definitions

YAML files that declare what regulatory controls require — which record types, what fields, what frequency. Zero evaluation logic. Configuration data consumed by downstream compliance tools.
YAML files that declare what regulatory controls require — which record types, required fields, and evidence frequency. Zero evaluation logic. Configuration data consumed by downstream compliance tools.

```yaml
controls:
- id: article-12
title: Record-Keeping
required_record_types: [tool_invocation, decision, guardrail_activation, permission_check]
required_fields: [record_id, timestamp, source, source_product, record_type, event, integrity.record_hash]
minimum_frequency: continuous
```

8 frameworks ship with v1:

Expand Down Expand Up @@ -314,18 +324,21 @@ CI pipelines: main, PR, determinism (cross-platform), CodeQL, nightly (hardening
## Install

```bash
# From source
go install github.com/Clyra-AI/proof/cmd/proof@v0.2.0
PROOF_VERSION="$(gh release view --repo Clyra-AI/proof --json tagName -q .tagName 2>/dev/null || curl -fsSL https://api.github.com/repos/Clyra-AI/proof/releases/latest | python3 -c 'import json,sys; print(json.load(sys.stdin)[\"tag_name\"])')"

# From module source at latest published release tag
go install github.com/Clyra-AI/proof/cmd/proof@"${PROOF_VERSION}"

# From release (after a tagged release is published)
gh release download vX.Y.Z -R Clyra-AI/proof -D /tmp/proof-release
# From release assets
gh release download "${PROOF_VERSION}" -R Clyra-AI/proof -D /tmp/proof-release
cd /tmp/proof-release && sha256sum -c checksums.txt
```

Go module:

```bash
go get github.com/Clyra-AI/proof@v0.2.0
PROOF_VERSION="$(gh release view --repo Clyra-AI/proof --json tagName -q .tagName 2>/dev/null || curl -fsSL https://api.github.com/repos/Clyra-AI/proof/releases/latest | python3 -c 'import json,sys; print(json.load(sys.stdin)[\"tag_name\"])')"
go get github.com/Clyra-AI/proof@"${PROOF_VERSION}"
```

## License
Expand Down
1 change: 1 addition & 0 deletions core/framework/colorado-ai-act.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ controls:
- id: co-risk
title: Risk Management
required_record_types: [risk_assessment]
required_fields: [record_id, timestamp, source, source_product, record_type, event, integrity.record_hash]
minimum_frequency: quarterly
3 changes: 3 additions & 0 deletions core/framework/eu-ai-act.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ controls:
- id: article-9
title: Risk Management
required_record_types: [risk_assessment]
required_fields: [record_id, timestamp, source, source_product, record_type, event, integrity.record_hash]
minimum_frequency: quarterly
- id: article-12
title: Record-Keeping
required_record_types: [tool_invocation, decision, guardrail_activation, permission_check]
required_fields: [record_id, timestamp, source, source_product, record_type, event, integrity.record_hash]
minimum_frequency: continuous
- id: article-14
title: Human Oversight
required_record_types: [human_oversight, approval]
required_fields: [record_id, timestamp, source, source_product, record_type, event, integrity.record_hash]
minimum_frequency: per-event
41 changes: 41 additions & 0 deletions core/framework/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,50 @@ func Load(idOrFile string) (*Framework, error) {
if f.Framework.ID == "" {
return nil, fmt.Errorf("framework %s missing id", idOrFile)
}
if len(f.Controls) == 0 {
return nil, fmt.Errorf("framework %s has no controls", idOrFile)
}
if err := validateControls(f.Controls, "controls"); err != nil {
return nil, fmt.Errorf("framework %s invalid: %w", idOrFile, err)
}
return &f, nil
}

func validateControls(controls []Control, path string) error {
for i, c := range controls {
controlPath := fmt.Sprintf("%s[%d]", path, i)
if strings.TrimSpace(c.ID) == "" {
return fmt.Errorf("%s missing id", controlPath)
}
if strings.TrimSpace(c.Title) == "" {
return fmt.Errorf("%s (%s) missing title", controlPath, c.ID)
}
if len(c.RequiredRecordTypes) == 0 {
return fmt.Errorf("%s (%s) missing required_record_types", controlPath, c.ID)
}
if strings.TrimSpace(c.MinimumFrequency) == "" {
return fmt.Errorf("%s (%s) missing minimum_frequency", controlPath, c.ID)
}
if len(c.RequiredFields) == 0 {
return fmt.Errorf("%s (%s) missing required_fields", controlPath, c.ID)
}
for _, t := range c.RequiredRecordTypes {
if strings.TrimSpace(t) == "" {
return fmt.Errorf("%s (%s) has blank required_record_types entry", controlPath, c.ID)
}
}
for _, field := range c.RequiredFields {
if strings.TrimSpace(field) == "" {
return fmt.Errorf("%s (%s) has blank required_fields entry", controlPath, c.ID)
}
}
if err := validateControls(c.Children, controlPath+".children"); err != nil {
return err
}
}
return nil
}

func countControls(in []Control) int {
total := 0
for _, c := range in {
Expand Down
45 changes: 45 additions & 0 deletions core/framework/framework_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package framework

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
Expand All @@ -27,3 +29,46 @@ func TestLoadMissingAndCountControls(t *testing.T) {
})
require.Equal(t, 4, total)
}

func TestValidateControls(t *testing.T) {
valid := []Control{
{
ID: "c1",
Title: "Control 1",
RequiredRecordTypes: []string{"decision"},
MinimumFrequency: "continuous",
RequiredFields: []string{"record_id", "event"},
},
}
require.NoError(t, validateControls(valid, "controls"))

missingFields := []Control{
{
ID: "c2",
Title: "Control 2",
RequiredRecordTypes: []string{"decision"},
MinimumFrequency: "continuous",
},
}
require.ErrorContains(t, validateControls(missingFields, "controls"), "missing required_fields")
}

func TestFrameworkCopiesStayInSync(t *testing.T) {
entries, err := os.ReadDir(".")
require.NoError(t, err)
for _, entry := range entries {
if entry.IsDir() {
continue
}
if filepath.Ext(entry.Name()) != ".yaml" {
continue
}
corePath := entry.Name()
repoPath := filepath.Join("..", "..", "frameworks", entry.Name())
coreRaw, err := os.ReadFile(corePath)
require.NoError(t, err)
repoRaw, err := os.ReadFile(repoPath)
require.NoError(t, err)
require.Equalf(t, string(repoRaw), string(coreRaw), "framework copy mismatch for %s", entry.Name())
}
}
1 change: 1 addition & 0 deletions core/framework/iso-42001.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ controls:
- id: aisms-risk
title: AI Risk Assessment
required_record_types: [risk_assessment, incident]
required_fields: [record_id, timestamp, source, source_product, record_type, event, integrity.record_hash]
minimum_frequency: quarterly
1 change: 1 addition & 0 deletions core/framework/nist-ai-600-1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ controls:
- id: nist-boundary
title: Boundary Enforcement
required_record_types: [policy_enforcement, permission_check]
required_fields: [record_id, timestamp, source, source_product, record_type, event, integrity.record_hash]
minimum_frequency: continuous
1 change: 1 addition & 0 deletions core/framework/pci-dss.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ controls:
- id: req-10
title: Logging and Monitoring
required_record_types: [tool_invocation, permission_check, incident]
required_fields: [record_id, timestamp, source, source_product, record_type, event, integrity.record_hash]
minimum_frequency: continuous
2 changes: 2 additions & 0 deletions core/framework/soc2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ controls:
- id: cc6
title: Logical Access
required_record_types: [permission_check, approval]
required_fields: [record_id, timestamp, source, source_product, record_type, event, integrity.record_hash]
minimum_frequency: continuous
- id: cc7
title: System Operations
required_record_types: [incident, guardrail_activation]
required_fields: [record_id, timestamp, source, source_product, record_type, event, integrity.record_hash]
minimum_frequency: continuous
1 change: 1 addition & 0 deletions core/framework/sox.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ controls:
- id: sox-cm
title: Change Management
required_record_types: [deployment, approval]
required_fields: [record_id, timestamp, source, source_product, record_type, event, integrity.record_hash]
minimum_frequency: per-change
1 change: 1 addition & 0 deletions core/framework/texas-traiga.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ controls:
- id: traiga-disclosure
title: AI Decision Disclosure
required_record_types: [decision, human_oversight]
required_fields: [record_id, timestamp, source, source_product, record_type, event, integrity.record_hash]
minimum_frequency: per-event
111 changes: 111 additions & 0 deletions core/schema/schema_sync_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package schema

import (
"os"
"path/filepath"
"sort"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

func TestSchemaTreeCopiesStayInSync(t *testing.T) {
coreRoot := filepath.Join("v1")
repoRoot := filepath.Join("..", "..", "schemas", "v1")

coreFiles, err := collectFiles(coreRoot)
require.NoError(t, err)
repoFiles, err := collectFiles(repoRoot)
require.NoError(t, err)

coreKeys := sortedKeys(coreFiles)
repoKeys := sortedKeys(repoFiles)
require.Equal(t, repoKeys, coreKeys, "schema file lists differ between core/schema/v1 and schemas/v1")

for _, rel := range coreKeys {
require.Equalf(t, string(repoFiles[rel]), string(coreFiles[rel]), "schema mismatch at %s", rel)
}
}

func TestBuiltinsMatchTypeSchemas(t *testing.T) {
typeDir := filepath.Join("v1", "types")
entries, err := os.ReadDir(typeDir)
require.NoError(t, err)

schemaFiles := make(map[string]struct{})
for _, entry := range entries {
if entry.IsDir() {
continue
}
name := entry.Name()
if !strings.HasSuffix(name, ".schema.json") {
continue
}
schemaFiles[name] = struct{}{}
}

require.NotEmpty(t, schemaFiles)
require.Equal(t, len(schemaFiles), len(builtins), "builtins count must match schema file count")

byFile := make(map[string]RecordType, len(builtins))
byName := make(map[string]struct{}, len(builtins))
for _, rt := range builtins {
require.NotEmpty(t, rt.Name)
require.NotEmpty(t, rt.SchemaPath)

_, seenName := byName[rt.Name]
require.Falsef(t, seenName, "duplicate built-in type name %s", rt.Name)
byName[rt.Name] = struct{}{}

base := filepath.Base(rt.SchemaPath)
_, exists := schemaFiles[base]
require.Truef(t, exists, "built-in type %s references missing schema file %s", rt.Name, base)
expected := strings.ReplaceAll(rt.Name, "_", "-") + ".schema.json"
require.Equalf(t, expected, base, "built-in schema path naming mismatch for %s", rt.Name)

_, seenFile := byFile[base]
require.Falsef(t, seenFile, "duplicate built-in schema mapping for %s", base)
byFile[base] = rt
}

for file := range schemaFiles {
_, exists := byFile[file]
require.Truef(t, exists, "schema file %s is not mapped in builtins", file)
}
}

func collectFiles(root string) (map[string][]byte, error) {
files := map[string][]byte{}
err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
raw, err := os.ReadFile(path)
if err != nil {
return err
}
rel, err := filepath.Rel(root, path)
if err != nil {
return err
}
files[filepath.ToSlash(rel)] = raw
return nil
})
if err != nil {
return nil, err
}
return files, nil
}

func sortedKeys(m map[string][]byte) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
sort.Strings(out)
return out
}
39 changes: 27 additions & 12 deletions core/schema/v1/framework-definition.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,31 @@
"$id": "https://github.com/Clyra-AI/proof/schemas/v1/framework-definition.schema.json",
"type": "object",
"required": ["framework", "controls"],
"definitions": {
"control": {
"type": "object",
"required": ["id", "title", "required_record_types", "required_fields", "minimum_frequency"],
"properties": {
"id": { "type": "string", "minLength": 1 },
"title": { "type": "string", "minLength": 1 },
"required_record_types": {
"type": "array",
"minItems": 1,
"items": { "type": "string", "minLength": 1 }
},
"minimum_frequency": { "type": "string", "minLength": 1 },
"required_fields": {
"type": "array",
"minItems": 1,
"items": { "type": "string", "minLength": 1 }
},
"children": {
"type": "array",
"items": { "$ref": "#/definitions/control" }
}
}
}
},
"properties": {
"framework": {
"type": "object",
Expand All @@ -15,18 +40,8 @@
},
"controls": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "title"],
"properties": {
"id": { "type": "string" },
"title": { "type": "string" },
"required_record_types": { "type": "array", "items": { "type": "string" } },
"minimum_frequency": { "type": "string" },
"required_fields": { "type": "array", "items": { "type": "string" } },
"children": { "type": "array", "items": { "type": "object" } }
}
}
"minItems": 1,
"items": { "$ref": "#/definitions/control" }
}
}
}
1 change: 1 addition & 0 deletions frameworks/colorado-ai-act.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ controls:
- id: co-risk
title: Risk Management
required_record_types: [risk_assessment]
required_fields: [record_id, timestamp, source, source_product, record_type, event, integrity.record_hash]
minimum_frequency: quarterly
Loading
Loading