From b09d005f7997cdd6001df7e60f129d4b0875ecde Mon Sep 17 00:00:00 2001 From: David Ahmann Date: Wed, 18 Feb 2026 13:40:01 -0500 Subject: [PATCH] Add scenario framework and compatibility alignment --- .github/workflows/main.yml | 20 ++ .tool-versions | 2 + CODEOWNERS | 5 + Makefile | 15 +- internal/scenarios/scenario_test.go | 194 ++++++++++++++++++ scenarios/CHANGELOG.md | 10 + scenarios/README.md | 7 + scenarios/proof/chain-round-trip/README.md | 3 + .../proof/chain-round-trip/expected.yaml | 3 + .../chain-round-trip/input-records.jsonl | 5 + .../proof/chain-tamper-detection/README.md | 3 + .../chain-tamper-detection/expected.yaml | 2 + .../input-records.jsonl | 10 + .../tamper-record-5.jsonl | 10 + .../proof/cross-product-mixed-chain/README.md | 3 + .../axym-records.jsonl | 5 + .../cross-product-mixed-chain/expected.yaml | 3 + .../gait-records.jsonl | 5 + .../wrkr-records.jsonl | 5 + .../proof/schema-validation-reject/README.md | 3 + .../schema-validation-reject/expected.yaml | 5 + .../invalid-record-bad-timestamp.json | 13 ++ .../invalid-record-missing-type.json | 11 + .../proof/signing-verify-round-trip/README.md | 3 + .../signing-verify-round-trip/expected.yaml | 3 + .../input-record.json | 18 ++ 26 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 .tool-versions create mode 100644 CODEOWNERS create mode 100644 internal/scenarios/scenario_test.go create mode 100644 scenarios/CHANGELOG.md create mode 100644 scenarios/README.md create mode 100644 scenarios/proof/chain-round-trip/README.md create mode 100644 scenarios/proof/chain-round-trip/expected.yaml create mode 100644 scenarios/proof/chain-round-trip/input-records.jsonl create mode 100644 scenarios/proof/chain-tamper-detection/README.md create mode 100644 scenarios/proof/chain-tamper-detection/expected.yaml create mode 100644 scenarios/proof/chain-tamper-detection/input-records.jsonl create mode 100644 scenarios/proof/chain-tamper-detection/tamper-record-5.jsonl create mode 100644 scenarios/proof/cross-product-mixed-chain/README.md create mode 100644 scenarios/proof/cross-product-mixed-chain/axym-records.jsonl create mode 100644 scenarios/proof/cross-product-mixed-chain/expected.yaml create mode 100644 scenarios/proof/cross-product-mixed-chain/gait-records.jsonl create mode 100644 scenarios/proof/cross-product-mixed-chain/wrkr-records.jsonl create mode 100644 scenarios/proof/schema-validation-reject/README.md create mode 100644 scenarios/proof/schema-validation-reject/expected.yaml create mode 100644 scenarios/proof/schema-validation-reject/invalid-record-bad-timestamp.json create mode 100644 scenarios/proof/schema-validation-reject/invalid-record-missing-type.json create mode 100644 scenarios/proof/signing-verify-round-trip/README.md create mode 100644 scenarios/proof/signing-verify-round-trip/expected.yaml create mode 100644 scenarios/proof/signing-verify-round-trip/input-record.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8e4217b..9686c6b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,3 +48,23 @@ jobs: with: go-version-file: go.mod - run: go build ./cmd/proof + + gait-compat: + name: Gait compatibility + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - name: Clone gait + run: git clone https://github.com/Clyra-AI/gait.git /tmp/gait + - name: Point gait at proof HEAD + run: | + cd /tmp/gait + go mod edit -replace github.com/Clyra-AI/proof=$GITHUB_WORKSPACE + go mod tidy + - name: Build gait + run: cd /tmp/gait && go build ./cmd/gait + - name: Test gait + run: cd /tmp/gait && go test ./... diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..8472001 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +golang 1.25.7 +python 3.13.7 diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..67fa0d1 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,5 @@ +# Default owner — all files +* @davidahmann + +# Scenario fixtures — human approval required for expected outcome changes +scenarios/ @davidahmann diff --git a/Makefile b/Makefile index 13ae9e9..5ad8db0 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ BINARY := proof +GOLANGCI_LINT_VERSION := v2.0.1 +GOSEC_VERSION := v2.23.0 +GOVULNCHECK_VERSION := v1.1.4 -.PHONY: fmt lint test build tidy contract coverage hooks prepush prepush-full test-integration test-e2e test-acceptance test-hardening test-chaos test-performance test-soak test-uat-local +.PHONY: fmt lint lint-full test build tidy contract coverage hooks prepush prepush-full test-integration test-e2e test-acceptance test-hardening test-chaos test-performance test-soak test-scenarios test-uat-local fmt: gofmt -w . @@ -9,6 +12,12 @@ lint: go vet ./... @if command -v golangci-lint >/dev/null 2>&1; then golangci-lint run ./...; fi +lint-full: + go vet ./... + go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) && golangci-lint run ./... + go install github.com/securego/gosec/v2/cmd/gosec@$(GOSEC_VERSION) && gosec ./... + go install golang.org/x/vuln/cmd/govulncheck@$(GOVULNCHECK_VERSION) && govulncheck ./... + gotest: go test ./... @@ -50,6 +59,7 @@ prepush-full: $(MAKE) test-chaos $(MAKE) test-performance $(MAKE) test-soak + $(MAKE) test-scenarios test-integration: ./scripts/test_integration.sh @@ -72,5 +82,8 @@ test-performance: test-soak: ./scripts/test_soak.sh +test-scenarios: + go test ./internal/scenarios -count=1 -tags=scenario -v + test-uat-local: ./scripts/test_uat_local.sh diff --git a/internal/scenarios/scenario_test.go b/internal/scenarios/scenario_test.go new file mode 100644 index 0000000..c411618 --- /dev/null +++ b/internal/scenarios/scenario_test.go @@ -0,0 +1,194 @@ +//go:build scenario + +package scenarios_test + +import ( + "bufio" + "encoding/hex" + "encoding/json" + "io" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + "strings" + "testing" + + "github.com/Clyra-AI/proof" + "github.com/Clyra-AI/proof/internal/testutil" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +type expectedInput struct { + File string `yaml:"file"` + ExitCode int `yaml:"exit_code"` +} + +type expectedScenario struct { + Verify string `yaml:"verify"` + Count int `yaml:"count"` + Chain string `yaml:"chain"` + BreakPoint int `yaml:"break_point"` + Sign string `yaml:"sign"` + Algorithm string `yaml:"algorithm"` + Total int `yaml:"total"` + Sources []string `yaml:"sources"` + InvalidInputs []expectedInput `yaml:"invalid_inputs"` +} + +func TestScenarios(t *testing.T) { + root := testutil.RepoRoot(t) + scenarioDir := filepath.Join(root, "scenarios", "proof") + + entries, err := os.ReadDir(scenarioDir) + if err != nil { + t.Fatalf("read scenario dir: %v", err) + } + + binary := testutil.BuildBinary(t, root) + + for _, entry := range entries { + if !entry.IsDir() { + continue + } + t.Run(entry.Name(), func(t *testing.T) { + dir := filepath.Join(scenarioDir, entry.Name()) + runScenario(t, binary, dir) + }) + } +} + +func runScenario(t *testing.T, binary, dir string) { + t.Helper() + expected := loadExpected(t, filepath.Join(dir, "expected.yaml")) + name := filepath.Base(dir) + + switch name { + case "chain-round-trip": + require.Equal(t, "pass", expected.Verify) + require.Equal(t, "intact", expected.Chain) + out, code := runProof(binary, "verify", dir) + require.Equal(t, 0, code, out) + require.Contains(t, out, "Chain intact") + require.Contains(t, out, strconv.Itoa(expected.Count)+" records") + + case "chain-tamper-detection": + require.Equal(t, "fail", expected.Verify) + tempDir := t.TempDir() + copyFile(t, filepath.Join(dir, "tamper-record-5.jsonl"), filepath.Join(tempDir, "records.jsonl")) + out, code := runProof(binary, "verify", tempDir) + require.Equal(t, 2, code, out) + require.Contains(t, out, "chain verification failed at index") + if expected.BreakPoint > 0 { + re := regexp.MustCompile(`index ([0-9]+)`) + match := re.FindStringSubmatch(out) + require.Len(t, match, 2, "missing break index in output: %s", out) + index, err := strconv.Atoi(match[1]) + require.NoError(t, err) + require.Equal(t, expected.BreakPoint, index+1) + } + + case "signing-verify-round-trip": + require.Equal(t, "success", expected.Sign) + require.Equal(t, "pass", expected.Verify) + require.Equal(t, "ed25519", expected.Algorithm) + recordPath := filepath.Join(dir, "input-record.json") + r, err := proof.ReadRecord(recordPath) + require.NoError(t, err) + key, err := proof.GenerateSigningKey() + require.NoError(t, err) + _, err = proof.Sign(r, key) + require.NoError(t, err) + require.NotEmpty(t, r.Integrity.SigningKeyID) + require.True(t, strings.HasPrefix(r.Integrity.Signature, "base64:")) + signedPath := filepath.Join(t.TempDir(), "signed-record.json") + require.NoError(t, proof.WriteRecord(signedPath, r)) + out, code := runProof(binary, "verify", "--signatures", "--public-key", hex.EncodeToString(key.Public), signedPath) + require.Equal(t, 0, code, out) + require.Contains(t, out, "Record verified") + + case "schema-validation-reject": + require.NotEmpty(t, expected.InvalidInputs) + for _, tc := range expected.InvalidInputs { + out, code := runProof(binary, "verify", filepath.Join(dir, tc.File)) + require.Equalf(t, tc.ExitCode, code, "file=%s output=%s", tc.File, out) + } + + case "cross-product-mixed-chain": + require.Equal(t, "pass", expected.Verify) + out, code := runProof(binary, "verify", dir) + require.Equal(t, 0, code, out) + require.Contains(t, out, "Chain intact") + require.Contains(t, out, strconv.Itoa(expected.Total)+" records") + foundSources := readSources(t, + filepath.Join(dir, "axym-records.jsonl"), + filepath.Join(dir, "gait-records.jsonl"), + filepath.Join(dir, "wrkr-records.jsonl"), + ) + for _, source := range expected.Sources { + _, ok := foundSources[source] + require.Truef(t, ok, "expected source %s not present", source) + } + + default: + t.Fatalf("unsupported scenario: %s", name) + } +} + +func loadExpected(t *testing.T, path string) expectedScenario { + t.Helper() + raw, err := os.ReadFile(path) + require.NoError(t, err) + var expected expectedScenario + require.NoError(t, yaml.Unmarshal(raw, &expected)) + return expected +} + +func runProof(binary string, args ...string) (string, int) { + cmd := exec.Command(binary, args...) // #nosec G204 -- test harness executes fixed binary with fixture args. + out, err := cmd.CombinedOutput() + if err == nil { + return string(out), 0 + } + exitErr, ok := err.(*exec.ExitError) + if !ok { + return string(out), 1 + } + return string(out), exitErr.ExitCode() +} + +func copyFile(t *testing.T, src, dst string) { + t.Helper() + in, err := os.Open(src) + require.NoError(t, err) + defer func() { _ = in.Close() }() + out, err := os.Create(dst) + require.NoError(t, err) + defer func() { _ = out.Close() }() + _, err = io.Copy(out, in) + require.NoError(t, err) +} + +func readSources(t *testing.T, paths ...string) map[string]struct{} { + t.Helper() + out := map[string]struct{}{} + for _, path := range paths { + f, err := os.Open(path) + require.NoError(t, err) + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + var record proof.Record + require.NoError(t, json.Unmarshal([]byte(line), &record)) + out[record.Source] = struct{}{} + } + require.NoError(t, scanner.Err()) + _ = f.Close() + } + return out +} diff --git a/scenarios/CHANGELOG.md b/scenarios/CHANGELOG.md new file mode 100644 index 0000000..baceee6 --- /dev/null +++ b/scenarios/CHANGELOG.md @@ -0,0 +1,10 @@ +# Scenario Changelog + +## 2026-02-18 + +- Added: chain-round-trip (validates basic chain append + verify) +- Added: chain-tamper-detection (validates integrity break on modified record) +- Added: signing-verify-round-trip (validates Ed25519 sign + verify cycle) +- Added: schema-validation-reject (validates exit code 6 on invalid input) +- Added: cross-product-mixed-chain (validates mixed-source chain integrity) +- Author: @davidahmann diff --git a/scenarios/README.md b/scenarios/README.md new file mode 100644 index 0000000..1231bea --- /dev/null +++ b/scenarios/README.md @@ -0,0 +1,7 @@ +# Scenarios + +Scenario fixtures for acceptance and regression checks. + +- Source of truth for expected outcomes lives in each scenario `expected.yaml`. +- Scenario tests run with `make test-scenarios`. +- Changes to expected outcomes require explicit human review (see `CODEOWNERS`). diff --git a/scenarios/proof/chain-round-trip/README.md b/scenarios/proof/chain-round-trip/README.md new file mode 100644 index 0000000..5dd63d0 --- /dev/null +++ b/scenarios/proof/chain-round-trip/README.md @@ -0,0 +1,3 @@ +# chain-round-trip + +Validates that a deterministic 5-record proof chain verifies as intact. diff --git a/scenarios/proof/chain-round-trip/expected.yaml b/scenarios/proof/chain-round-trip/expected.yaml new file mode 100644 index 0000000..ea6e8d4 --- /dev/null +++ b/scenarios/proof/chain-round-trip/expected.yaml @@ -0,0 +1,3 @@ +verify: pass +count: 5 +chain: intact diff --git a/scenarios/proof/chain-round-trip/input-records.jsonl b/scenarios/proof/chain-round-trip/input-records.jsonl new file mode 100644 index 0000000..8759fd0 --- /dev/null +++ b/scenarios/proof/chain-round-trip/input-records.jsonl @@ -0,0 +1,5 @@ +{"record_id":"prf-2026-02-18T10:00:00Z-7f4138c7","record_version":"1.0","timestamp":"2026-02-18T10:00:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"round-01","step":1},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:dbcd4333b4f6e585c01127435f6041189cd4742fdacea3c7a1a68362b08cd169"}} +{"record_id":"prf-2026-02-18T10:01:00Z-bcc3f471","record_version":"1.0","timestamp":"2026-02-18T10:01:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"round-02","step":2},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:1d4d093c3eea85d8b760f80d081d71db5dde78c148b0d8b2c2b31c544b604913","previous_record_hash":"sha256:dbcd4333b4f6e585c01127435f6041189cd4742fdacea3c7a1a68362b08cd169"}} +{"record_id":"prf-2026-02-18T10:02:00Z-8d03ac2b","record_version":"1.0","timestamp":"2026-02-18T10:02:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"round-03","step":3},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:434e0c1ececfb2b39e8f4e79c2ef97e4f65450484a43c6c65a992ff01310f127","previous_record_hash":"sha256:1d4d093c3eea85d8b760f80d081d71db5dde78c148b0d8b2c2b31c544b604913"}} +{"record_id":"prf-2026-02-18T10:03:00Z-bbd81b6e","record_version":"1.0","timestamp":"2026-02-18T10:03:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"round-04","step":4},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:b41571d029ce11db86e53b34caae57f1cc5d4f2edf7637e365bf72e72797518b","previous_record_hash":"sha256:434e0c1ececfb2b39e8f4e79c2ef97e4f65450484a43c6c65a992ff01310f127"}} +{"record_id":"prf-2026-02-18T10:04:00Z-b621f095","record_version":"1.0","timestamp":"2026-02-18T10:04:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"round-05","step":5},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:70158f8bcf65bce0d4fdba876fc7ce52f0a719e5479b1859fa80e68a77fabd45","previous_record_hash":"sha256:b41571d029ce11db86e53b34caae57f1cc5d4f2edf7637e365bf72e72797518b"}} diff --git a/scenarios/proof/chain-tamper-detection/README.md b/scenarios/proof/chain-tamper-detection/README.md new file mode 100644 index 0000000..81ae700 --- /dev/null +++ b/scenarios/proof/chain-tamper-detection/README.md @@ -0,0 +1,3 @@ +# chain-tamper-detection + +Validates that tampering a single record causes chain verification failure. diff --git a/scenarios/proof/chain-tamper-detection/expected.yaml b/scenarios/proof/chain-tamper-detection/expected.yaml new file mode 100644 index 0000000..8d28e86 --- /dev/null +++ b/scenarios/proof/chain-tamper-detection/expected.yaml @@ -0,0 +1,2 @@ +verify: fail +break_point: 5 diff --git a/scenarios/proof/chain-tamper-detection/input-records.jsonl b/scenarios/proof/chain-tamper-detection/input-records.jsonl new file mode 100644 index 0000000..9092c18 --- /dev/null +++ b/scenarios/proof/chain-tamper-detection/input-records.jsonl @@ -0,0 +1,10 @@ +{"record_id":"prf-2026-02-18T11:00:00Z-a29b4457","record_version":"1.0","timestamp":"2026-02-18T11:00:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-01","step":1},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:0a23896e6ea5cdf3b3fcaaab3bab323a051c02fe255a76f8ccc804e5041b4ff8"}} +{"record_id":"prf-2026-02-18T11:01:00Z-29d61a07","record_version":"1.0","timestamp":"2026-02-18T11:01:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-02","step":2},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:c6fa7ba16a8acbf37af31e01f9bcfdec9b9f57676b629c7afce529550325cbb7","previous_record_hash":"sha256:0a23896e6ea5cdf3b3fcaaab3bab323a051c02fe255a76f8ccc804e5041b4ff8"}} +{"record_id":"prf-2026-02-18T11:02:00Z-32d00e63","record_version":"1.0","timestamp":"2026-02-18T11:02:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-03","step":3},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:7e63b79e7e86191aadf004764e16905c17ecfbf50908b0fc3359eefa55766d99","previous_record_hash":"sha256:c6fa7ba16a8acbf37af31e01f9bcfdec9b9f57676b629c7afce529550325cbb7"}} +{"record_id":"prf-2026-02-18T11:03:00Z-c37ddca3","record_version":"1.0","timestamp":"2026-02-18T11:03:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-04","step":4},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:613710ef6c376396c1307f9bd8038caeec42fd2815afa0949ae35922e494cb6b","previous_record_hash":"sha256:7e63b79e7e86191aadf004764e16905c17ecfbf50908b0fc3359eefa55766d99"}} +{"record_id":"prf-2026-02-18T11:04:00Z-4a4e7aeb","record_version":"1.0","timestamp":"2026-02-18T11:04:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-05","step":5},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:877b3a9adff59ab666f35e05f7e355225a961571df7079f0d43f4dc9389672b3","previous_record_hash":"sha256:613710ef6c376396c1307f9bd8038caeec42fd2815afa0949ae35922e494cb6b"}} +{"record_id":"prf-2026-02-18T11:05:00Z-fc749276","record_version":"1.0","timestamp":"2026-02-18T11:05:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-06","step":6},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:0070ac6d9499e2190aa116d700f6e3810282d03d8ba95e12153385523b5f0435","previous_record_hash":"sha256:877b3a9adff59ab666f35e05f7e355225a961571df7079f0d43f4dc9389672b3"}} +{"record_id":"prf-2026-02-18T11:06:00Z-24535ec5","record_version":"1.0","timestamp":"2026-02-18T11:06:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-07","step":7},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:f94039b9e9571b0ab5fcaf4caf29f714718927edc48f4b76b6da47bd5d2d3d61","previous_record_hash":"sha256:0070ac6d9499e2190aa116d700f6e3810282d03d8ba95e12153385523b5f0435"}} +{"record_id":"prf-2026-02-18T11:07:00Z-4403e0e0","record_version":"1.0","timestamp":"2026-02-18T11:07:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-08","step":8},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:053911e06a74e327bb97fb262a7ecf02664ca5f4344eb8340dde86e5c8ee830b","previous_record_hash":"sha256:f94039b9e9571b0ab5fcaf4caf29f714718927edc48f4b76b6da47bd5d2d3d61"}} +{"record_id":"prf-2026-02-18T11:08:00Z-10918428","record_version":"1.0","timestamp":"2026-02-18T11:08:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-09","step":9},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:5f7499fb7ead8616a2782125f3420977e169ec5fe65da37aa253d1acd4e10bfd","previous_record_hash":"sha256:053911e06a74e327bb97fb262a7ecf02664ca5f4344eb8340dde86e5c8ee830b"}} +{"record_id":"prf-2026-02-18T11:09:00Z-fa03e16a","record_version":"1.0","timestamp":"2026-02-18T11:09:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-10","step":10},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:f7d125de6c13347e9c257e054bef42e68faed86f4554a5d1bd38b8bba41e197a","previous_record_hash":"sha256:5f7499fb7ead8616a2782125f3420977e169ec5fe65da37aa253d1acd4e10bfd"}} diff --git a/scenarios/proof/chain-tamper-detection/tamper-record-5.jsonl b/scenarios/proof/chain-tamper-detection/tamper-record-5.jsonl new file mode 100644 index 0000000..32200a7 --- /dev/null +++ b/scenarios/proof/chain-tamper-detection/tamper-record-5.jsonl @@ -0,0 +1,10 @@ +{"record_id":"prf-2026-02-18T11:00:00Z-a29b4457","record_version":"1.0","timestamp":"2026-02-18T11:00:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-01","step":1},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:0a23896e6ea5cdf3b3fcaaab3bab323a051c02fe255a76f8ccc804e5041b4ff8"}} +{"record_id":"prf-2026-02-18T11:01:00Z-29d61a07","record_version":"1.0","timestamp":"2026-02-18T11:01:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-02","step":2},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:c6fa7ba16a8acbf37af31e01f9bcfdec9b9f57676b629c7afce529550325cbb7","previous_record_hash":"sha256:0a23896e6ea5cdf3b3fcaaab3bab323a051c02fe255a76f8ccc804e5041b4ff8"}} +{"record_id":"prf-2026-02-18T11:02:00Z-32d00e63","record_version":"1.0","timestamp":"2026-02-18T11:02:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-03","step":3},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:7e63b79e7e86191aadf004764e16905c17ecfbf50908b0fc3359eefa55766d99","previous_record_hash":"sha256:c6fa7ba16a8acbf37af31e01f9bcfdec9b9f57676b629c7afce529550325cbb7"}} +{"record_id":"prf-2026-02-18T11:03:00Z-c37ddca3","record_version":"1.0","timestamp":"2026-02-18T11:03:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-04","step":4},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:613710ef6c376396c1307f9bd8038caeec42fd2815afa0949ae35922e494cb6b","previous_record_hash":"sha256:7e63b79e7e86191aadf004764e16905c17ecfbf50908b0fc3359eefa55766d99"}} +{"record_id":"prf-2026-02-18T11:04:00Z-4a4e7aeb","record_version":"1.0","timestamp":"2026-02-18T11:04:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tampered-05","step":5},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:877b3a9adff59ab666f35e05f7e355225a961571df7079f0d43f4dc9389672b3","previous_record_hash":"sha256:613710ef6c376396c1307f9bd8038caeec42fd2815afa0949ae35922e494cb6b"}} +{"record_id":"prf-2026-02-18T11:05:00Z-fc749276","record_version":"1.0","timestamp":"2026-02-18T11:05:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-06","step":6},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:0070ac6d9499e2190aa116d700f6e3810282d03d8ba95e12153385523b5f0435","previous_record_hash":"sha256:877b3a9adff59ab666f35e05f7e355225a961571df7079f0d43f4dc9389672b3"}} +{"record_id":"prf-2026-02-18T11:06:00Z-24535ec5","record_version":"1.0","timestamp":"2026-02-18T11:06:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-07","step":7},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:f94039b9e9571b0ab5fcaf4caf29f714718927edc48f4b76b6da47bd5d2d3d61","previous_record_hash":"sha256:0070ac6d9499e2190aa116d700f6e3810282d03d8ba95e12153385523b5f0435"}} +{"record_id":"prf-2026-02-18T11:07:00Z-4403e0e0","record_version":"1.0","timestamp":"2026-02-18T11:07:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-08","step":8},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:053911e06a74e327bb97fb262a7ecf02664ca5f4344eb8340dde86e5c8ee830b","previous_record_hash":"sha256:f94039b9e9571b0ab5fcaf4caf29f714718927edc48f4b76b6da47bd5d2d3d61"}} +{"record_id":"prf-2026-02-18T11:08:00Z-10918428","record_version":"1.0","timestamp":"2026-02-18T11:08:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-09","step":9},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:5f7499fb7ead8616a2782125f3420977e169ec5fe65da37aa253d1acd4e10bfd","previous_record_hash":"sha256:053911e06a74e327bb97fb262a7ecf02664ca5f4344eb8340dde86e5c8ee830b"}} +{"record_id":"prf-2026-02-18T11:09:00Z-fa03e16a","record_version":"1.0","timestamp":"2026-02-18T11:09:00Z","source":"proof","source_product":"proof","record_type":"decision","event":{"event_id":"tamper-10","step":10},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:f7d125de6c13347e9c257e054bef42e68faed86f4554a5d1bd38b8bba41e197a","previous_record_hash":"sha256:5f7499fb7ead8616a2782125f3420977e169ec5fe65da37aa253d1acd4e10bfd"}} diff --git a/scenarios/proof/cross-product-mixed-chain/README.md b/scenarios/proof/cross-product-mixed-chain/README.md new file mode 100644 index 0000000..bdc32ac --- /dev/null +++ b/scenarios/proof/cross-product-mixed-chain/README.md @@ -0,0 +1,3 @@ +# cross-product-mixed-chain + +Validates that mixed-source records chain and verify with deterministic integrity. diff --git a/scenarios/proof/cross-product-mixed-chain/axym-records.jsonl b/scenarios/proof/cross-product-mixed-chain/axym-records.jsonl new file mode 100644 index 0000000..d9acd83 --- /dev/null +++ b/scenarios/proof/cross-product-mixed-chain/axym-records.jsonl @@ -0,0 +1,5 @@ +{"record_id":"prf-2026-02-18T13:00:00Z-c1694925","record_version":"1.0","timestamp":"2026-02-18T13:00:00Z","source":"axym","source_product":"axym","record_type":"tool_invocation","event":{"event_id":"axym-01","tool":"db_query"},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:39ac7fad09284f33f18437a6dc346ba0c220e861cd7c40d399a68c6fbe40dccf"}} +{"record_id":"prf-2026-02-18T13:01:00Z-0d28b699","record_version":"1.0","timestamp":"2026-02-18T13:01:00Z","source":"axym","source_product":"axym","record_type":"tool_invocation","event":{"event_id":"axym-02","tool":"db_query"},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:d7a17cc9819596a0549a2c68729fbdeb6b843c15b05555b54b2aa919fb1cd1fd","previous_record_hash":"sha256:39ac7fad09284f33f18437a6dc346ba0c220e861cd7c40d399a68c6fbe40dccf"}} +{"record_id":"prf-2026-02-18T13:02:00Z-fc60f996","record_version":"1.0","timestamp":"2026-02-18T13:02:00Z","source":"axym","source_product":"axym","record_type":"tool_invocation","event":{"event_id":"axym-03","tool":"db_query"},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:5dff5a17e2315e02518ac127222cf1f2c5c31892d47fa4e8483de9d33ad9db80","previous_record_hash":"sha256:d7a17cc9819596a0549a2c68729fbdeb6b843c15b05555b54b2aa919fb1cd1fd"}} +{"record_id":"prf-2026-02-18T13:03:00Z-8314783d","record_version":"1.0","timestamp":"2026-02-18T13:03:00Z","source":"axym","source_product":"axym","record_type":"tool_invocation","event":{"event_id":"axym-04","tool":"db_query"},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:0a31338a076350f5ee45ee075d49c7d46ba1eb87a391beaa26a429f0f11b5c16","previous_record_hash":"sha256:5dff5a17e2315e02518ac127222cf1f2c5c31892d47fa4e8483de9d33ad9db80"}} +{"record_id":"prf-2026-02-18T13:04:00Z-6955abf7","record_version":"1.0","timestamp":"2026-02-18T13:04:00Z","source":"axym","source_product":"axym","record_type":"tool_invocation","event":{"event_id":"axym-05","tool":"db_query"},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:52fe611424be01db0dfd7c19675871418678178d30cd8594f5e1f22c95ed16cc","previous_record_hash":"sha256:0a31338a076350f5ee45ee075d49c7d46ba1eb87a391beaa26a429f0f11b5c16"}} diff --git a/scenarios/proof/cross-product-mixed-chain/expected.yaml b/scenarios/proof/cross-product-mixed-chain/expected.yaml new file mode 100644 index 0000000..5e8acf8 --- /dev/null +++ b/scenarios/proof/cross-product-mixed-chain/expected.yaml @@ -0,0 +1,3 @@ +verify: pass +total: 15 +sources: [wrkr, gait, axym] diff --git a/scenarios/proof/cross-product-mixed-chain/gait-records.jsonl b/scenarios/proof/cross-product-mixed-chain/gait-records.jsonl new file mode 100644 index 0000000..79be4f8 --- /dev/null +++ b/scenarios/proof/cross-product-mixed-chain/gait-records.jsonl @@ -0,0 +1,5 @@ +{"record_id":"prf-2026-02-18T13:05:00Z-35d0ed5f","record_version":"1.0","timestamp":"2026-02-18T13:05:00Z","source":"gait","source_product":"gait","record_type":"policy_enforcement","event":{"event_id":"gait-01","verdict":"allow"},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:bb1480dfd3e8acf161251c282db9a41d76a96005545945ac691e0db99b0a7c3b","previous_record_hash":"sha256:52fe611424be01db0dfd7c19675871418678178d30cd8594f5e1f22c95ed16cc"}} +{"record_id":"prf-2026-02-18T13:06:00Z-8ddc7b15","record_version":"1.0","timestamp":"2026-02-18T13:06:00Z","source":"gait","source_product":"gait","record_type":"policy_enforcement","event":{"event_id":"gait-02","verdict":"allow"},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:becb3dad9ed9d78eaada9e5ff364d34d3ca244953a8ecefe5d6879a3eb643207","previous_record_hash":"sha256:bb1480dfd3e8acf161251c282db9a41d76a96005545945ac691e0db99b0a7c3b"}} +{"record_id":"prf-2026-02-18T13:07:00Z-a61b0d37","record_version":"1.0","timestamp":"2026-02-18T13:07:00Z","source":"gait","source_product":"gait","record_type":"policy_enforcement","event":{"event_id":"gait-03","verdict":"allow"},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:c6d243b5fec3c626c342228490f3f514a026e25b88866a1b932b458979e4a993","previous_record_hash":"sha256:becb3dad9ed9d78eaada9e5ff364d34d3ca244953a8ecefe5d6879a3eb643207"}} +{"record_id":"prf-2026-02-18T13:08:00Z-a6613588","record_version":"1.0","timestamp":"2026-02-18T13:08:00Z","source":"gait","source_product":"gait","record_type":"policy_enforcement","event":{"event_id":"gait-04","verdict":"allow"},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:2595426f044d8f6738470de92a52a8bb40ec2ede6fb75a337dc3b3c4f41d027a","previous_record_hash":"sha256:c6d243b5fec3c626c342228490f3f514a026e25b88866a1b932b458979e4a993"}} +{"record_id":"prf-2026-02-18T13:09:00Z-fca43d9d","record_version":"1.0","timestamp":"2026-02-18T13:09:00Z","source":"gait","source_product":"gait","record_type":"policy_enforcement","event":{"event_id":"gait-05","verdict":"allow"},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:088f3908f0f3bbf7f5417a11aa7de2f80165d484ce53302d6efca59d50c97757","previous_record_hash":"sha256:2595426f044d8f6738470de92a52a8bb40ec2ede6fb75a337dc3b3c4f41d027a"}} diff --git a/scenarios/proof/cross-product-mixed-chain/wrkr-records.jsonl b/scenarios/proof/cross-product-mixed-chain/wrkr-records.jsonl new file mode 100644 index 0000000..d914b33 --- /dev/null +++ b/scenarios/proof/cross-product-mixed-chain/wrkr-records.jsonl @@ -0,0 +1,5 @@ +{"record_id":"prf-2026-02-18T13:10:00Z-764da835","record_version":"1.0","timestamp":"2026-02-18T13:10:00Z","source":"wrkr","source_product":"wrkr","record_type":"scan_finding","event":{"event_id":"wrkr-01","severity":"medium"},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:e415af6c121b059f6f337a466cf4eb865dc380fb95f2aa52d18be45fa21ae66e","previous_record_hash":"sha256:088f3908f0f3bbf7f5417a11aa7de2f80165d484ce53302d6efca59d50c97757"}} +{"record_id":"prf-2026-02-18T13:11:00Z-2c50188f","record_version":"1.0","timestamp":"2026-02-18T13:11:00Z","source":"wrkr","source_product":"wrkr","record_type":"scan_finding","event":{"event_id":"wrkr-02","severity":"medium"},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:75c72484bceaf070e55a61a9f4a36ad57eb73e59e2624c29735a94141d62c85f","previous_record_hash":"sha256:e415af6c121b059f6f337a466cf4eb865dc380fb95f2aa52d18be45fa21ae66e"}} +{"record_id":"prf-2026-02-18T13:12:00Z-ab138afa","record_version":"1.0","timestamp":"2026-02-18T13:12:00Z","source":"wrkr","source_product":"wrkr","record_type":"scan_finding","event":{"event_id":"wrkr-03","severity":"medium"},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:eae487c159239129debbccdb58307b57fce378bece4d189af42bf1df054646d7","previous_record_hash":"sha256:75c72484bceaf070e55a61a9f4a36ad57eb73e59e2624c29735a94141d62c85f"}} +{"record_id":"prf-2026-02-18T13:13:00Z-47b806a1","record_version":"1.0","timestamp":"2026-02-18T13:13:00Z","source":"wrkr","source_product":"wrkr","record_type":"scan_finding","event":{"event_id":"wrkr-04","severity":"medium"},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:fabcca1749529068f7e21f55e3f2b2ceae8d9632335f6251c92bee97a05aa2c5","previous_record_hash":"sha256:eae487c159239129debbccdb58307b57fce378bece4d189af42bf1df054646d7"}} +{"record_id":"prf-2026-02-18T13:14:00Z-51e44651","record_version":"1.0","timestamp":"2026-02-18T13:14:00Z","source":"wrkr","source_product":"wrkr","record_type":"scan_finding","event":{"event_id":"wrkr-05","severity":"medium"},"controls":{"permissions_enforced":true},"integrity":{"record_hash":"sha256:4f98b9d82501e84219202b961a14d5e48d31dc402ef6d6a0a4b8c4953b5e16a6","previous_record_hash":"sha256:fabcca1749529068f7e21f55e3f2b2ceae8d9632335f6251c92bee97a05aa2c5"}} diff --git a/scenarios/proof/schema-validation-reject/README.md b/scenarios/proof/schema-validation-reject/README.md new file mode 100644 index 0000000..ee26ff4 --- /dev/null +++ b/scenarios/proof/schema-validation-reject/README.md @@ -0,0 +1,3 @@ +# schema-validation-reject + +Validates invalid input handling for malformed/unsupported record payloads. diff --git a/scenarios/proof/schema-validation-reject/expected.yaml b/scenarios/proof/schema-validation-reject/expected.yaml new file mode 100644 index 0000000..2a2c787 --- /dev/null +++ b/scenarios/proof/schema-validation-reject/expected.yaml @@ -0,0 +1,5 @@ +invalid_inputs: + - file: invalid-record-missing-type.json + exit_code: 6 + - file: invalid-record-bad-timestamp.json + exit_code: 6 diff --git a/scenarios/proof/schema-validation-reject/invalid-record-bad-timestamp.json b/scenarios/proof/schema-validation-reject/invalid-record-bad-timestamp.json new file mode 100644 index 0000000..5dfb1ef --- /dev/null +++ b/scenarios/proof/schema-validation-reject/invalid-record-bad-timestamp.json @@ -0,0 +1,13 @@ +{ + "record_id": "prf-invalid-timestamp", + "record_version": "1.0", + "timestamp": "not-a-timestamp", + "source": "schema-test", + "source_product": "proof", + "record_type": "decision", + "event": {"action": "allow"}, + "controls": {}, + "integrity": { + "record_hash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + } +} diff --git a/scenarios/proof/schema-validation-reject/invalid-record-missing-type.json b/scenarios/proof/schema-validation-reject/invalid-record-missing-type.json new file mode 100644 index 0000000..1223f51 --- /dev/null +++ b/scenarios/proof/schema-validation-reject/invalid-record-missing-type.json @@ -0,0 +1,11 @@ +{ + "record_version": "1.0", + "timestamp": "2026-02-18T10:00:00Z", + "source": "schema-test", + "source_product": "proof", + "event": {"action": "allow"}, + "controls": {}, + "integrity": { + "record_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } +} diff --git a/scenarios/proof/signing-verify-round-trip/README.md b/scenarios/proof/signing-verify-round-trip/README.md new file mode 100644 index 0000000..f10b69d --- /dev/null +++ b/scenarios/proof/signing-verify-round-trip/README.md @@ -0,0 +1,3 @@ +# signing-verify-round-trip + +Validates Ed25519 signing and CLI verification with `--signatures`. diff --git a/scenarios/proof/signing-verify-round-trip/expected.yaml b/scenarios/proof/signing-verify-round-trip/expected.yaml new file mode 100644 index 0000000..4bc487c --- /dev/null +++ b/scenarios/proof/signing-verify-round-trip/expected.yaml @@ -0,0 +1,3 @@ +sign: success +verify: pass +algorithm: ed25519 diff --git a/scenarios/proof/signing-verify-round-trip/input-record.json b/scenarios/proof/signing-verify-round-trip/input-record.json new file mode 100644 index 0000000..8d24b64 --- /dev/null +++ b/scenarios/proof/signing-verify-round-trip/input-record.json @@ -0,0 +1,18 @@ +{ + "record_id": "prf-2026-02-18T12:00:00Z-a85a3b15", + "record_version": "1.0", + "timestamp": "2026-02-18T12:00:00Z", + "source": "proof", + "source_product": "proof", + "record_type": "tool_invocation", + "event": { + "action": "read", + "tool": "filesystem_read" + }, + "controls": { + "permissions_enforced": true + }, + "integrity": { + "record_hash": "sha256:1755a5813924d92563761e37127941dbee4175329b8dbaa6eeb35d2d9eee7a29" + } +} \ No newline at end of file