diff --git a/.gitignore b/.gitignore index 543b12c..da63fd4 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,6 @@ health.json # Go modules vendor/ -go.sum # Python __pycache__/ diff --git a/contracts/howl/SOURCE.md b/contracts/howl/SOURCE.md new file mode 100644 index 0000000..c923494 --- /dev/null +++ b/contracts/howl/SOURCE.md @@ -0,0 +1,45 @@ +# Vendored `howl.*` contract schemas + +The five `.schema.json` files in this directory are vendored, byte-for-byte +copies of the generated JSON Schema published by HowlDream. They are the +authoritative shape of the `howl.*` ecosystem envelopes this repo consumes +and produces (`howl.candidate/v1` in, `howl.assessment/v1` out). + +**Source repository:** https://github.com/howlcipher/howldream +**Pinned commit:** `bd10b18b7182e5216b494d728d015dbbba9b32d0` +**Source path:** `schemas/*.schema.json` +**Vendored:** 2026-09-12 + +## Why vendored, not fetched at build/runtime + +See `howldream`'s `schemas/README.md` for the full comparison of distribution +models. Summary: all producing/consuming repositories are owned by the same +org with full git access, so a pinned, vendored copy (re-vendored as a +deliberate, visible diff) is the simplest architecture justified by current +scale — no live network fetch, no new shared package/repo. + +## How to re-vendor + +```sh +cp /path/to/howldream/schemas/howl.*.schema.json contracts/howl/ +``` + +Then update the pinned commit above to the exact `howldream` commit the +copied files came from. Re-vendoring is a deliberate act, not automatic — +bumping the pin should be its own visible diff, so a schema change is never +silently absorbed. + +## What this proves, and what it doesn't + +`contract_test.go` in this package validates real envelope fixtures against +these vendored schemas using a pure-Go JSON Schema validator +(`github.com/santhosh-tekuri/jsonschema/v5`) — **no Python, no `howldream` +import, no network access at test time.** This is the cross-language contract +test referenced in `howldream/issues.md` item 1: proof that the schema is +useful to a consumer that has never seen HowlDream's Python implementation. + +As documented in `howldream`'s `AUTHORITY_INVARIANT.md`, schema validation +proves an envelope is *shaped* correctly — it does not prove the envelope's +claims are true. HowlFrame's own `apps/candidate_evaluator` is the runtime +control that independently evaluates candidate claims; it does not trust a +candidate's self-reported `trust`/`status`/`disposition` fields. diff --git a/contracts/howl/contract_test.go b/contracts/howl/contract_test.go new file mode 100644 index 0000000..7028674 --- /dev/null +++ b/contracts/howl/contract_test.go @@ -0,0 +1,151 @@ +// Cross-language contract test: proves the vendored howl.* JSON Schema is +// genuinely useful to a consumer that has never seen HowlDream's Python +// implementation. This test imports no Python, no `howldream` package, and +// makes no network calls — only the local vendored schema files and this +// pure-Go JSON Schema validator. +package howl + +import ( + "encoding/json" + "testing" + + "github.com/santhosh-tekuri/jsonschema/v5" +) + +// validExplorationResult is a representative, schema-conformant +// howl.exploration_result/v1 envelope, including a nested candidate. +const validExplorationResult = `{ + "schema_version": "howl.exploration_result/v1", + "exploration_id": "exp-go-001", + "parent_request_id": "req-go-001", + "objective": "Explore alternative diagnostic strategies", + "originating_component": "howlplane", + "authority": {"type": "ADVISORY", "executable": false}, + "candidates": [ + { + "schema_version": "howl.candidate/v1", + "candidate_id": "cand-go-001", + "source_run_id": "run-go-001", + "parent_request_id": "req-go-001", + "objective": "Explore alternative diagnostic strategies", + "text": "IDEA: add a bounded retry with jitter", + "trust": "UNVERIFIED", + "status": "GENERATED", + "authority": {"type": "ADVISORY", "executable": false}, + "provenance": {"run_id": "run-go-001", "producer_component": "howldream"} + } + ], + "verification_status": "UNVERIFIED", + "recommended_disposition": "DEFER", + "provenance": {"run_id": "run-go-001", "producer_component": "howldream"} +}` + +func compileExplorationResult(t *testing.T) *jsonschema.Schema { + t.Helper() + schema, err := Compile(ExplorationResult) + if err != nil { + t.Fatalf("compile %s: %v", ExplorationResult, err) + } + return schema +} + +// mutate returns a deep copy of validExplorationResult with the given +// mutator applied to its decoded form, re-encoded to JSON. +func mutate(t *testing.T, mutator func(env map[string]interface{})) []byte { + t.Helper() + var env map[string]interface{} + if err := json.Unmarshal([]byte(validExplorationResult), &env); err != nil { + t.Fatalf("unmarshal fixture: %v", err) + } + mutator(env) + out, err := json.Marshal(env) + if err != nil { + t.Fatalf("marshal mutated fixture: %v", err) + } + return out +} + +func TestValidEnvelopeValidates(t *testing.T) { + schema := compileExplorationResult(t) + if err := ValidateJSON(schema, []byte(validExplorationResult)); err != nil { + t.Fatalf("expected valid envelope to validate, got: %v", err) + } +} + +func TestMissingRequiredFieldFails(t *testing.T) { + schema := compileExplorationResult(t) + bad := mutate(t, func(env map[string]interface{}) { + delete(env, "objective") + }) + if err := ValidateJSON(schema, bad); err == nil { + t.Fatal("expected missing required field 'objective' to fail validation") + } +} + +func TestUnsupportedSchemaVersionFails(t *testing.T) { + schema := compileExplorationResult(t) + bad := mutate(t, func(env map[string]interface{}) { + env["schema_version"] = "howl.bogus/v99" + }) + if err := ValidateJSON(schema, bad); err == nil { + t.Fatal("expected forged schema_version to fail validation") + } +} + +func TestInvalidEnumFails(t *testing.T) { + schema := compileExplorationResult(t) + bad := mutate(t, func(env map[string]interface{}) { + env["verification_status"] = "VERIFIED" + }) + if err := ValidateJSON(schema, bad); err == nil { + t.Fatal("expected out-of-enum verification_status to fail validation") + } +} + +func TestForgedAuthorityExecutableFails(t *testing.T) { + schema := compileExplorationResult(t) + bad := mutate(t, func(env map[string]interface{}) { + env["authority"] = map[string]interface{}{"type": "ADVISORY", "executable": true} + }) + if err := ValidateJSON(schema, bad); err == nil { + t.Fatal("expected authority.executable=true to fail validation") + } +} + +func TestInjectedPrivilegedFieldFails(t *testing.T) { + schema := compileExplorationResult(t) + for _, field := range []string{"executor", "execution_capability", "approved", "bypass_review"} { + field := field + t.Run(field, func(t *testing.T) { + bad := mutate(t, func(env map[string]interface{}) { + env[field] = true + }) + if err := ValidateJSON(schema, bad); err == nil { + t.Fatalf("expected injected privileged field %q to fail validation (additionalProperties: false)", field) + } + }) + } +} + +func TestNestedCandidateForgedTrustFails(t *testing.T) { + schema := compileExplorationResult(t) + bad := mutate(t, func(env map[string]interface{}) { + candidates := env["candidates"].([]interface{}) + cand := candidates[0].(map[string]interface{}) + cand["trust"] = "VERIFIED" + }) + if err := ValidateJSON(schema, bad); err == nil { + t.Fatal("expected nested candidate with forged trust='VERIFIED' to fail validation") + } +} + +func TestAllFiveVendoredSchemasCompile(t *testing.T) { + for _, name := range []string{Exploration, Candidate, Assessment, DevelopmentResult, ExplorationResult} { + name := name + t.Run(name, func(t *testing.T) { + if _, err := Compile(name); err != nil { + t.Fatalf("expected vendored schema %s to compile, got: %v", name, err) + } + }) + } +} diff --git a/contracts/howl/howl.assessment.v1.schema.json b/contracts/howl/howl.assessment.v1.schema.json new file mode 100644 index 0000000..9e1ecb3 --- /dev/null +++ b/contracts/howl/howl.assessment.v1.schema.json @@ -0,0 +1,196 @@ +{ + "$defs": { + "ExplorationAuthority": { + "additionalProperties": false, + "description": "Explicit authority boundary. HowlDream artifacts cannot authorize execution.", + "properties": { + "executable": { + "const": false, + "default": false, + "title": "Executable", + "type": "boolean" + }, + "type": { + "const": "ADVISORY", + "default": "ADVISORY", + "title": "Type", + "type": "string" + } + }, + "title": "ExplorationAuthority", + "type": "object" + }, + "Provenance": { + "additionalProperties": true, + "description": "Shared audit-trail metadata attached to every howl.* envelope.\n\nDeliberately open (extra=\"allow\"), unlike StrictModel: provenance is\ndescriptive audit metadata, not an authority or structural boundary, and\nproducers have historically attached ad hoc extra keys (e.g. request-\nspecific hashes). The named fields below are the canonical, cross-cutting\nconcepts every producer should populate; anything else stays as an\nunvalidated extra key rather than failing validation.\n\nConcepts deliberately NOT duplicated here because a dedicated top-level\nfield already covers them on the envelopes that need them: authority\nstate (`authority`), schema identity (`schema_version`), candidate/run\nlineage (`parent_request_id`, `source_run_id`, `candidate_id`,\n`descent_dag`), and per-envelope evaluation/verification state\n(`CandidateHandoff.status`, `CandidateAssessment.disposition`,\n`ExplorationResult.verification_status`).", + "properties": { + "created_at": { + "title": "Created At", + "type": "string" + }, + "model_or_provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Model Or Provider" + }, + "observation_kind": { + "anyOf": [ + { + "enum": [ + "SIMULATED", + "DETERMINISTIC", + "LIVE", + "EXTERNALLY_OBSERVED" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Observation Kind" + }, + "producer_component": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Producer Component" + }, + "producer_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Producer Version" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "transformations": { + "items": { + "type": "string" + }, + "title": "Transformations", + "type": "array" + } + }, + "title": "Provenance", + "type": "object" + } + }, + "$id": "https://raw.githubusercontent.com/howlcipher/howldream/main/schemas/howl.assessment.v1.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "description": "Downstream evaluation result from HowlFrame: howl.assessment/v1.", + "properties": { + "assessment_id": { + "maxLength": 100, + "minLength": 1, + "title": "Assessment Id", + "type": "string" + }, + "authority": { + "$ref": "#/$defs/ExplorationAuthority" + }, + "candidate_id": { + "maxLength": 200, + "minLength": 1, + "title": "Candidate Id", + "type": "string" + }, + "confidence": { + "default": "LOW", + "enum": [ + "LOW", + "MEDIUM", + "HIGH", + "UNKNOWN" + ], + "title": "Confidence", + "type": "string" + }, + "contradictions": { + "items": { + "type": "string" + }, + "title": "Contradictions", + "type": "array" + }, + "disposition": { + "enum": [ + "REJECT", + "UNRESOLVED", + "INVESTIGATE", + "ACCEPT_FOR_DEVELOPMENT" + ], + "title": "Disposition", + "type": "string" + }, + "evidence": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Evidence", + "type": "array" + }, + "limitations": { + "items": { + "type": "string" + }, + "title": "Limitations", + "type": "array" + }, + "provenance": { + "$ref": "#/$defs/Provenance" + }, + "schema_version": { + "const": "howl.assessment/v1", + "default": "howl.assessment/v1", + "title": "Schema Version", + "type": "string" + }, + "unresolved_claims": { + "items": { + "type": "string" + }, + "title": "Unresolved Claims", + "type": "array" + } + }, + "required": [ + "assessment_id", + "candidate_id", + "disposition" + ], + "title": "HowlFrame Candidate Assessment (howl.assessment/v1)", + "type": "object" +} diff --git a/contracts/howl/howl.candidate.v1.schema.json b/contracts/howl/howl.candidate.v1.schema.json new file mode 100644 index 0000000..449be7a --- /dev/null +++ b/contracts/howl/howl.candidate.v1.schema.json @@ -0,0 +1,233 @@ +{ + "$defs": { + "ExplorationAuthority": { + "additionalProperties": false, + "description": "Explicit authority boundary. HowlDream artifacts cannot authorize execution.", + "properties": { + "executable": { + "const": false, + "default": false, + "title": "Executable", + "type": "boolean" + }, + "type": { + "const": "ADVISORY", + "default": "ADVISORY", + "title": "Type", + "type": "string" + } + }, + "title": "ExplorationAuthority", + "type": "object" + }, + "Provenance": { + "additionalProperties": true, + "description": "Shared audit-trail metadata attached to every howl.* envelope.\n\nDeliberately open (extra=\"allow\"), unlike StrictModel: provenance is\ndescriptive audit metadata, not an authority or structural boundary, and\nproducers have historically attached ad hoc extra keys (e.g. request-\nspecific hashes). The named fields below are the canonical, cross-cutting\nconcepts every producer should populate; anything else stays as an\nunvalidated extra key rather than failing validation.\n\nConcepts deliberately NOT duplicated here because a dedicated top-level\nfield already covers them on the envelopes that need them: authority\nstate (`authority`), schema identity (`schema_version`), candidate/run\nlineage (`parent_request_id`, `source_run_id`, `candidate_id`,\n`descent_dag`), and per-envelope evaluation/verification state\n(`CandidateHandoff.status`, `CandidateAssessment.disposition`,\n`ExplorationResult.verification_status`).", + "properties": { + "created_at": { + "title": "Created At", + "type": "string" + }, + "model_or_provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Model Or Provider" + }, + "observation_kind": { + "anyOf": [ + { + "enum": [ + "SIMULATED", + "DETERMINISTIC", + "LIVE", + "EXTERNALLY_OBSERVED" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Observation Kind" + }, + "producer_component": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Producer Component" + }, + "producer_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Producer Version" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "transformations": { + "items": { + "type": "string" + }, + "title": "Transformations", + "type": "array" + } + }, + "title": "Provenance", + "type": "object" + } + }, + "$id": "https://raw.githubusercontent.com/howlcipher/howldream/main/schemas/howl.candidate.v1.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "description": "Speculative candidate for cross-component evaluation: howl.candidate/v1.", + "properties": { + "assumptions": { + "items": { + "type": "string" + }, + "title": "Assumptions", + "type": "array" + }, + "authority": { + "$ref": "#/$defs/ExplorationAuthority" + }, + "candidate_id": { + "maxLength": 200, + "minLength": 1, + "title": "Candidate Id", + "type": "string" + }, + "claims": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Claims", + "type": "array" + }, + "condition": { + "default": "dream", + "title": "Condition", + "type": "string" + }, + "contradictions": { + "items": { + "type": "string" + }, + "title": "Contradictions", + "type": "array" + }, + "evidence_refs": { + "items": { + "type": "string" + }, + "title": "Evidence Refs", + "type": "array" + }, + "objective": { + "maxLength": 10000, + "minLength": 1, + "title": "Objective", + "type": "string" + }, + "parent_request_id": { + "maxLength": 100, + "minLength": 1, + "title": "Parent Request Id", + "type": "string" + }, + "provenance": { + "$ref": "#/$defs/Provenance" + }, + "schema_version": { + "const": "howl.candidate/v1", + "default": "howl.candidate/v1", + "title": "Schema Version", + "type": "string" + }, + "source_run_id": { + "maxLength": 100, + "minLength": 1, + "title": "Source Run Id", + "type": "string" + }, + "status": { + "default": "GENERATED", + "enum": [ + "GENERATED", + "CHALLENGED", + "LOCALLY_VERIFIED", + "FRAME_REVIEWED", + "UNRESOLVED", + "REJECTED" + ], + "title": "Status", + "type": "string" + }, + "text": { + "maxLength": 50000, + "minLength": 1, + "title": "Text", + "type": "string" + }, + "trust": { + "const": "UNVERIFIED", + "default": "UNVERIFIED", + "title": "Trust", + "type": "string" + }, + "unresolved_issues": { + "items": { + "type": "string" + }, + "title": "Unresolved Issues", + "type": "array" + }, + "verified_constraints": { + "items": { + "type": "string" + }, + "title": "Verified Constraints", + "type": "array" + } + }, + "required": [ + "candidate_id", + "source_run_id", + "parent_request_id", + "objective", + "text" + ], + "title": "HowlDream Candidate Handoff (howl.candidate/v1)", + "type": "object" +} diff --git a/contracts/howl/howl.development_result.v1.schema.json b/contracts/howl/howl.development_result.v1.schema.json new file mode 100644 index 0000000..71f135f --- /dev/null +++ b/contracts/howl/howl.development_result.v1.schema.json @@ -0,0 +1,201 @@ +{ + "$defs": { + "ExplorationAuthority": { + "additionalProperties": false, + "description": "Explicit authority boundary. HowlDream artifacts cannot authorize execution.", + "properties": { + "executable": { + "const": false, + "default": false, + "title": "Executable", + "type": "boolean" + }, + "type": { + "const": "ADVISORY", + "default": "ADVISORY", + "title": "Type", + "type": "string" + } + }, + "title": "ExplorationAuthority", + "type": "object" + }, + "Provenance": { + "additionalProperties": true, + "description": "Shared audit-trail metadata attached to every howl.* envelope.\n\nDeliberately open (extra=\"allow\"), unlike StrictModel: provenance is\ndescriptive audit metadata, not an authority or structural boundary, and\nproducers have historically attached ad hoc extra keys (e.g. request-\nspecific hashes). The named fields below are the canonical, cross-cutting\nconcepts every producer should populate; anything else stays as an\nunvalidated extra key rather than failing validation.\n\nConcepts deliberately NOT duplicated here because a dedicated top-level\nfield already covers them on the envelopes that need them: authority\nstate (`authority`), schema identity (`schema_version`), candidate/run\nlineage (`parent_request_id`, `source_run_id`, `candidate_id`,\n`descent_dag`), and per-envelope evaluation/verification state\n(`CandidateHandoff.status`, `CandidateAssessment.disposition`,\n`ExplorationResult.verification_status`).", + "properties": { + "created_at": { + "title": "Created At", + "type": "string" + }, + "model_or_provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Model Or Provider" + }, + "observation_kind": { + "anyOf": [ + { + "enum": [ + "SIMULATED", + "DETERMINISTIC", + "LIVE", + "EXTERNALLY_OBSERVED" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Observation Kind" + }, + "producer_component": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Producer Component" + }, + "producer_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Producer Version" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "transformations": { + "items": { + "type": "string" + }, + "title": "Transformations", + "type": "array" + } + }, + "title": "Provenance", + "type": "object" + } + }, + "$id": "https://raw.githubusercontent.com/howlcipher/howldream/main/schemas/howl.development_result.v1.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "description": "Deliberate sandbox prototype specification from HowlCreate: howl.development_result/v1.", + "properties": { + "architecture_proposal": { + "default": "", + "maxLength": 100000, + "title": "Architecture Proposal", + "type": "string" + }, + "authority": { + "$ref": "#/$defs/ExplorationAuthority" + }, + "development_id": { + "maxLength": 200, + "minLength": 1, + "title": "Development Id", + "type": "string" + }, + "epistemic_status": { + "maxLength": 100, + "minLength": 1, + "title": "Epistemic Status", + "type": "string" + }, + "execution_authority": { + "const": "NONE", + "default": "NONE", + "title": "Execution Authority", + "type": "string" + }, + "idea": { + "additionalProperties": true, + "title": "Idea", + "type": "object" + }, + "lineage": { + "additionalProperties": true, + "title": "Lineage", + "type": "object" + }, + "origin": { + "default": "howldream", + "maxLength": 100, + "minLength": 1, + "title": "Origin", + "type": "string" + }, + "parent_request_id": { + "maxLength": 100, + "minLength": 1, + "title": "Parent Request Id", + "type": "string" + }, + "provenance": { + "$ref": "#/$defs/Provenance" + }, + "sandbox_prototype_design": { + "additionalProperties": true, + "title": "Sandbox Prototype Design", + "type": "object" + }, + "schema_version": { + "const": "howl.development_result/v1", + "default": "howl.development_result/v1", + "title": "Schema Version", + "type": "string" + }, + "source_candidate_id": { + "maxLength": 200, + "minLength": 1, + "title": "Source Candidate Id", + "type": "string" + }, + "test_specification": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Test Specification", + "type": "array" + } + }, + "required": [ + "development_id", + "source_candidate_id", + "parent_request_id", + "epistemic_status" + ], + "title": "HowlCreate Development Result (howl.development_result/v1)", + "type": "object" +} diff --git a/contracts/howl/howl.exploration.v1.schema.json b/contracts/howl/howl.exploration.v1.schema.json new file mode 100644 index 0000000..387924b --- /dev/null +++ b/contracts/howl/howl.exploration.v1.schema.json @@ -0,0 +1,283 @@ +{ + "$defs": { + "EvidenceRef": { + "additionalProperties": false, + "description": "Evidence supplied to or extracted during exploration.", + "properties": { + "facts": { + "additionalProperties": { + "type": "string" + }, + "title": "Facts", + "type": "object" + }, + "id": { + "pattern": "^[a-zA-Z0-9_./-]{1,100}$", + "title": "Id", + "type": "string" + }, + "text": { + "default": "", + "maxLength": 50000, + "title": "Text", + "type": "string" + } + }, + "required": [ + "id" + ], + "title": "EvidenceRef", + "type": "object" + }, + "ExplorationAuthority": { + "additionalProperties": false, + "description": "Explicit authority boundary. HowlDream artifacts cannot authorize execution.", + "properties": { + "executable": { + "const": false, + "default": false, + "title": "Executable", + "type": "boolean" + }, + "type": { + "const": "ADVISORY", + "default": "ADVISORY", + "title": "Type", + "type": "string" + } + }, + "title": "ExplorationAuthority", + "type": "object" + }, + "ExplorationBudget": { + "additionalProperties": false, + "description": "Finite bounded resource budget for speculative exploration.", + "properties": { + "local_only": { + "default": true, + "title": "Local Only", + "type": "boolean" + }, + "max_candidates": { + "default": 6, + "maximum": 50, + "minimum": 1, + "title": "Max Candidates", + "type": "integer" + }, + "max_duration_seconds": { + "default": 120, + "maximum": 600, + "minimum": 1, + "title": "Max Duration Seconds", + "type": "integer" + }, + "max_tokens": { + "default": 512, + "maximum": 4096, + "minimum": 32, + "title": "Max Tokens", + "type": "integer" + }, + "max_trials": { + "default": 1, + "maximum": 10, + "minimum": 1, + "title": "Max Trials", + "type": "integer" + }, + "provider_allowlist": { + "items": { + "type": "string" + }, + "title": "Provider Allowlist", + "type": "array" + } + }, + "title": "ExplorationBudget", + "type": "object" + }, + "Provenance": { + "additionalProperties": true, + "description": "Shared audit-trail metadata attached to every howl.* envelope.\n\nDeliberately open (extra=\"allow\"), unlike StrictModel: provenance is\ndescriptive audit metadata, not an authority or structural boundary, and\nproducers have historically attached ad hoc extra keys (e.g. request-\nspecific hashes). The named fields below are the canonical, cross-cutting\nconcepts every producer should populate; anything else stays as an\nunvalidated extra key rather than failing validation.\n\nConcepts deliberately NOT duplicated here because a dedicated top-level\nfield already covers them on the envelopes that need them: authority\nstate (`authority`), schema identity (`schema_version`), candidate/run\nlineage (`parent_request_id`, `source_run_id`, `candidate_id`,\n`descent_dag`), and per-envelope evaluation/verification state\n(`CandidateHandoff.status`, `CandidateAssessment.disposition`,\n`ExplorationResult.verification_status`).", + "properties": { + "created_at": { + "title": "Created At", + "type": "string" + }, + "model_or_provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Model Or Provider" + }, + "observation_kind": { + "anyOf": [ + { + "enum": [ + "SIMULATED", + "DETERMINISTIC", + "LIVE", + "EXTERNALLY_OBSERVED" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Observation Kind" + }, + "producer_component": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Producer Component" + }, + "producer_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Producer Version" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "transformations": { + "items": { + "type": "string" + }, + "title": "Transformations", + "type": "array" + } + }, + "title": "Provenance", + "type": "object" + } + }, + "$id": "https://raw.githubusercontent.com/howlcipher/howldream/main/schemas/howl.exploration.v1.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "description": "Standardized machine-readable exploration envelope: howl.exploration/v1.", + "properties": { + "authority": { + "$ref": "#/$defs/ExplorationAuthority" + }, + "budget": { + "$ref": "#/$defs/ExplorationBudget" + }, + "constraints": { + "items": { + "type": "string" + }, + "maxItems": 50, + "title": "Constraints", + "type": "array" + }, + "context_refs": { + "items": { + "type": "string" + }, + "maxItems": 20, + "title": "Context Refs", + "type": "array" + }, + "evidence_refs": { + "items": { + "$ref": "#/$defs/EvidenceRef" + }, + "maxItems": 100, + "title": "Evidence Refs", + "type": "array" + }, + "objective": { + "maxLength": 10000, + "minLength": 1, + "title": "Objective", + "type": "string" + }, + "originating_component": { + "default": "howlplane", + "maxLength": 100, + "minLength": 1, + "title": "Originating Component", + "type": "string" + }, + "parent_run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Parent Run Id" + }, + "provenance": { + "$ref": "#/$defs/Provenance" + }, + "request_id": { + "pattern": "^[a-zA-Z0-9_-]{1,100}$", + "title": "Request Id", + "type": "string" + }, + "requested_mode": { + "default": "dream", + "enum": [ + "dream", + "nightmare", + "paired" + ], + "title": "Requested Mode", + "type": "string" + }, + "risk_class": { + "default": "EXPLORATORY", + "maxLength": 50, + "title": "Risk Class", + "type": "string" + }, + "schema_version": { + "const": "howl.exploration/v1", + "default": "howl.exploration/v1", + "title": "Schema Version", + "type": "string" + } + }, + "required": [ + "request_id", + "objective" + ], + "title": "HowlDream Exploration Request (howl.exploration/v1)", + "type": "object" +} diff --git a/contracts/howl/howl.exploration_result.v1.schema.json b/contracts/howl/howl.exploration_result.v1.schema.json new file mode 100644 index 0000000..94ca697 --- /dev/null +++ b/contracts/howl/howl.exploration_result.v1.schema.json @@ -0,0 +1,467 @@ +{ + "$defs": { + "CandidateHandoff": { + "additionalProperties": false, + "description": "Speculative candidate for cross-component evaluation: howl.candidate/v1.", + "properties": { + "assumptions": { + "items": { + "type": "string" + }, + "title": "Assumptions", + "type": "array" + }, + "authority": { + "$ref": "#/$defs/ExplorationAuthority" + }, + "candidate_id": { + "maxLength": 200, + "minLength": 1, + "title": "Candidate Id", + "type": "string" + }, + "claims": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Claims", + "type": "array" + }, + "condition": { + "default": "dream", + "title": "Condition", + "type": "string" + }, + "contradictions": { + "items": { + "type": "string" + }, + "title": "Contradictions", + "type": "array" + }, + "evidence_refs": { + "items": { + "type": "string" + }, + "title": "Evidence Refs", + "type": "array" + }, + "objective": { + "maxLength": 10000, + "minLength": 1, + "title": "Objective", + "type": "string" + }, + "parent_request_id": { + "maxLength": 100, + "minLength": 1, + "title": "Parent Request Id", + "type": "string" + }, + "provenance": { + "$ref": "#/$defs/Provenance" + }, + "schema_version": { + "const": "howl.candidate/v1", + "default": "howl.candidate/v1", + "title": "Schema Version", + "type": "string" + }, + "source_run_id": { + "maxLength": 100, + "minLength": 1, + "title": "Source Run Id", + "type": "string" + }, + "status": { + "default": "GENERATED", + "enum": [ + "GENERATED", + "CHALLENGED", + "LOCALLY_VERIFIED", + "FRAME_REVIEWED", + "UNRESOLVED", + "REJECTED" + ], + "title": "Status", + "type": "string" + }, + "text": { + "maxLength": 50000, + "minLength": 1, + "title": "Text", + "type": "string" + }, + "trust": { + "const": "UNVERIFIED", + "default": "UNVERIFIED", + "title": "Trust", + "type": "string" + }, + "unresolved_issues": { + "items": { + "type": "string" + }, + "title": "Unresolved Issues", + "type": "array" + }, + "verified_constraints": { + "items": { + "type": "string" + }, + "title": "Verified Constraints", + "type": "array" + } + }, + "required": [ + "candidate_id", + "source_run_id", + "parent_request_id", + "objective", + "text" + ], + "title": "CandidateHandoff", + "type": "object" + }, + "DescentDAG": { + "additionalProperties": false, + "description": "Durable lineage DAG recording exploration descent and surviving handoffs.", + "properties": { + "edges": { + "items": { + "$ref": "#/$defs/DescentEdge" + }, + "title": "Edges", + "type": "array" + }, + "max_branching_factor": { + "default": 50, + "maximum": 200, + "minimum": 1, + "title": "Max Branching Factor", + "type": "integer" + }, + "max_depth": { + "default": 20, + "maximum": 100, + "minimum": 1, + "title": "Max Depth", + "type": "integer" + }, + "nodes": { + "additionalProperties": { + "$ref": "#/$defs/DescentNode" + }, + "title": "Nodes", + "type": "object" + } + }, + "title": "DescentDAG", + "type": "object" + }, + "DescentEdge": { + "additionalProperties": false, + "description": "Directed derivation edge in the descent DAG.", + "properties": { + "relation": { + "maxLength": 100, + "minLength": 1, + "title": "Relation", + "type": "string" + }, + "source": { + "maxLength": 200, + "minLength": 1, + "title": "Source", + "type": "string" + }, + "target": { + "maxLength": 200, + "minLength": 1, + "title": "Target", + "type": "string" + } + }, + "required": [ + "source", + "target", + "relation" + ], + "title": "DescentEdge", + "type": "object" + }, + "DescentNode": { + "additionalProperties": false, + "description": "Single node in the durable descent DAG.", + "properties": { + "created_at": { + "title": "Created At", + "type": "string" + }, + "details": { + "additionalProperties": true, + "title": "Details", + "type": "object" + }, + "label": { + "maxLength": 500, + "minLength": 1, + "title": "Label", + "type": "string" + }, + "node_id": { + "maxLength": 200, + "minLength": 1, + "title": "Node Id", + "type": "string" + }, + "node_type": { + "enum": [ + "OBJECTIVE", + "BASELINE", + "DREAM_RUN", + "CANDIDATE", + "NIGHTMARE_CHALLENGE", + "WAKE_VERIFICATION", + "FRAME_ASSESSMENT", + "CREATE_PROJECT" + ], + "title": "Node Type", + "type": "string" + } + }, + "required": [ + "node_id", + "node_type", + "label" + ], + "title": "DescentNode", + "type": "object" + }, + "ExplorationAuthority": { + "additionalProperties": false, + "description": "Explicit authority boundary. HowlDream artifacts cannot authorize execution.", + "properties": { + "executable": { + "const": false, + "default": false, + "title": "Executable", + "type": "boolean" + }, + "type": { + "const": "ADVISORY", + "default": "ADVISORY", + "title": "Type", + "type": "string" + } + }, + "title": "ExplorationAuthority", + "type": "object" + }, + "Provenance": { + "additionalProperties": true, + "description": "Shared audit-trail metadata attached to every howl.* envelope.\n\nDeliberately open (extra=\"allow\"), unlike StrictModel: provenance is\ndescriptive audit metadata, not an authority or structural boundary, and\nproducers have historically attached ad hoc extra keys (e.g. request-\nspecific hashes). The named fields below are the canonical, cross-cutting\nconcepts every producer should populate; anything else stays as an\nunvalidated extra key rather than failing validation.\n\nConcepts deliberately NOT duplicated here because a dedicated top-level\nfield already covers them on the envelopes that need them: authority\nstate (`authority`), schema identity (`schema_version`), candidate/run\nlineage (`parent_request_id`, `source_run_id`, `candidate_id`,\n`descent_dag`), and per-envelope evaluation/verification state\n(`CandidateHandoff.status`, `CandidateAssessment.disposition`,\n`ExplorationResult.verification_status`).", + "properties": { + "created_at": { + "title": "Created At", + "type": "string" + }, + "model_or_provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Model Or Provider" + }, + "observation_kind": { + "anyOf": [ + { + "enum": [ + "SIMULATED", + "DETERMINISTIC", + "LIVE", + "EXTERNALLY_OBSERVED" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Observation Kind" + }, + "producer_component": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Producer Component" + }, + "producer_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Producer Version" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "transformations": { + "items": { + "type": "string" + }, + "title": "Transformations", + "type": "array" + } + }, + "title": "Provenance", + "type": "object" + } + }, + "$id": "https://raw.githubusercontent.com/howlcipher/howldream/main/schemas/howl.exploration_result.v1.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "description": "Complete machine-readable outcome returned by HowlDream: howl.exploration_result/v1.", + "properties": { + "authority": { + "$ref": "#/$defs/ExplorationAuthority" + }, + "candidates": { + "items": { + "$ref": "#/$defs/CandidateHandoff" + }, + "title": "Candidates", + "type": "array" + }, + "claims": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Claims", + "type": "array" + }, + "contradictions": { + "items": { + "type": "string" + }, + "title": "Contradictions", + "type": "array" + }, + "descent_dag": { + "$ref": "#/$defs/DescentDAG" + }, + "evidence": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Evidence", + "type": "array" + }, + "exploration_id": { + "maxLength": 100, + "minLength": 1, + "title": "Exploration Id", + "type": "string" + }, + "objective": { + "maxLength": 10000, + "minLength": 1, + "title": "Objective", + "type": "string" + }, + "originating_component": { + "maxLength": 100, + "minLength": 1, + "title": "Originating Component", + "type": "string" + }, + "parent_request_id": { + "maxLength": 100, + "minLength": 1, + "title": "Parent Request Id", + "type": "string" + }, + "provenance": { + "$ref": "#/$defs/Provenance" + }, + "recommended_disposition": { + "default": "DEFER", + "enum": [ + "INVESTIGATE", + "REJECT", + "DEFER", + "ACCEPT_FOR_DEVELOPMENT" + ], + "title": "Recommended Disposition", + "type": "string" + }, + "schema_version": { + "const": "howl.exploration_result/v1", + "default": "howl.exploration_result/v1", + "title": "Schema Version", + "type": "string" + }, + "scores": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Scores", + "type": "array" + }, + "unresolved_assumptions": { + "items": { + "type": "string" + }, + "title": "Unresolved Assumptions", + "type": "array" + }, + "verification_status": { + "default": "UNVERIFIED", + "enum": [ + "UNVERIFIED", + "REQUIRES_DOWNSTREAM_REVIEW", + "LOCALLY_VERIFIED" + ], + "title": "Verification Status", + "type": "string" + } + }, + "required": [ + "exploration_id", + "parent_request_id", + "objective", + "originating_component" + ], + "title": "HowlDream Exploration Result (howl.exploration_result/v1)", + "type": "object" +} diff --git a/contracts/howl/howl.go b/contracts/howl/howl.go new file mode 100644 index 0000000..3ebb147 --- /dev/null +++ b/contracts/howl/howl.go @@ -0,0 +1,68 @@ +// Package howl loads the vendored howl.* ecosystem contract schemas and +// compiles them for validation. The schema files in this directory are +// vendored, generated copies from HowlDream — see SOURCE.md. This package +// never imports or shells out to howldream; it only reads the local JSON +// Schema files. +package howl + +import ( + "bytes" + "embed" + "encoding/json" + "fmt" + + "github.com/santhosh-tekuri/jsonschema/v5" +) + +//go:embed *.schema.json +var schemaFS embed.FS + +// Names of the vendored schema files, keyed by their howl.* family slug. +const ( + Exploration = "howl.exploration.v1.schema.json" + Candidate = "howl.candidate.v1.schema.json" + Assessment = "howl.assessment.v1.schema.json" + DevelopmentResult = "howl.development_result.v1.schema.json" + ExplorationResult = "howl.exploration_result.v1.schema.json" +) + +// Compile loads and compiles the named vendored schema file (one of the +// constants above). It registers the schema under its own declared $id so +// internal $ref resolution works with no network access. +func Compile(fileName string) (*jsonschema.Schema, error) { + raw, err := schemaFS.ReadFile(fileName) + if err != nil { + return nil, fmt.Errorf("read vendored schema %s: %w", fileName, err) + } + + var doc struct { + ID string `json:"$id"` + } + if err := json.Unmarshal(raw, &doc); err != nil { + return nil, fmt.Errorf("parse $id from %s: %w", fileName, err) + } + if doc.ID == "" { + return nil, fmt.Errorf("schema %s has no $id", fileName) + } + + compiler := jsonschema.NewCompiler() + if err := compiler.AddResource(doc.ID, bytes.NewReader(raw)); err != nil { + return nil, fmt.Errorf("register schema resource %s: %w", doc.ID, err) + } + schema, err := compiler.Compile(doc.ID) + if err != nil { + return nil, fmt.Errorf("compile schema %s: %w", doc.ID, err) + } + return schema, nil +} + +// ValidateJSON decodes raw JSON and validates it against the compiled schema. +func ValidateJSON(schema *jsonschema.Schema, raw []byte) error { + decoder := json.NewDecoder(bytes.NewReader(raw)) + decoder.UseNumber() + var v interface{} + if err := decoder.Decode(&v); err != nil { + return fmt.Errorf("decode envelope JSON: %w", err) + } + return schema.Validate(v) +} diff --git a/go.mod b/go.mod index 8cbbe6d..c5fbee4 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/howlcipher/howlframe go 1.21 + +require github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0daed30 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=