diff --git a/CHANGELOG.md b/CHANGELOG.md index 39e8bcf..1c7c806 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Thi ## [Unreleased] ### Added +- KnowledgeNugget contract v0.1 (the estate's L2 content grain): warrant-typed knowledge fragments generalizing the production IFM warrant-typed extraction — content-addressed source spans (`docRef` + span + `sha256-` content hash), a closed four-kind warrant taxonomy (`direct-quote`/`computed`/`inferred`/`model-generated`) with evidence refs and confidence, ontology type refs (`kkoTypeRefs`), normalized `canonicalPayload`, typed provenance chain links, and `wallTime`/`logicalTime` carried verbatim from the MPCC `ConversationEvent` envelope (parity machine-enforced). Normative: `model-generated` nuggets MUST stay visibly distinguishable downstream (admissibility discounting), and `computed`/`inferred` warrants must cite evidence (schema-enforced). Includes direct-quote and model-generated examples, negative conformance vectors (`fixtures/knowledge-nugget/conformance.json`), a `validate-knowledge-nugget-examples` target, and a normative spec note (`specs/knowledge-nugget-contract.md`). +- SemanticAction contract v0.1 (the declarative typed-action registry for the NL→plan compiler): ontology-typed inputs and output (KKO concept URIs recommended, shared with `KnowledgeNugget.kkoTypeRefs`), a closed constraint taxonomy (`subClassOf`/`instanceOf`/`sameAs` — polymorphism via subsumption), executor binding, registry metadata (owner + deprecated), and a two-value effect posture: actions are side-effect-free at plan-search time, and `sideEffects: "effect-request"` declares the executor emits an MPCC `EffectRequest` and defers to an `EffectDecision` rather than acting directly — no direct-mutation vocabulary exists (validator-pinned). Includes pure-lookup and effect-request examples, negative conformance vectors (`fixtures/semantic-action/conformance.json`), a `validate-semantic-action-examples` target, and a normative spec note (`specs/semantic-action-contract.md`). - MPCC event contract v0.1 (conversation + trading event family): `ConversationEvent` (the canonical 26-field conversation-fabric event — causal parents, authority context with delegation chain, visibility scope, requested/approved/actual effect references, modality, speech act), `EffectRequest` / `EffectDecision` / `EffectRecord` (the requested → approved → actual → compensated effect lifecycle with idempotency keys, referencing `PolicyDecision` / `ExecutionDecision` rather than duplicating them), `NullAbsenceRecord` (12-kind null/absence taxonomy), and the trading families `MarketDataEvent`, `OrderIntent`, `ExecutionReport`, `PositionChange`, `ReconciliationRecord` as profiles of the ConversationEvent envelope (one shared envelope vocabulary; parity machine-enforced). Includes canonical examples telling one end-to-end governed trade story, negative conformance vectors (`fixtures/mpcc-event-contract/conformance.json`), a `validate-mpcc-event-examples` target, and a normative spec note (`specs/mpcc-event-contract.md`). Provenance: SocioProphet/profit-mpcc, hardened to the policy-integrity tranche-0001 strictness bar. - Epistemic Assay contracts: `ReasoningAssay` (a typed verdict on a claim over five orthogonal axes — method, binding, verifier, agreement, authority — whose `ok`/`sad`/`bad` `projectedState` is a render-time projection, not a stored scalar) and `AssayStandard` (a verifier's measured, versioned reliability — the calibration reference every assay must point at). Includes canonical `ok`/`sad`/`bad` examples, ADR (`docs/adr/ADR-epistemic-assay-verdict-v0-1.md`), and a `validate-reasoning-examples` target that also enforces projection soundness (recomputes `assay()` from the stored axes and fails on drift) and retroactively brings the existing reasoning family under `make validate`. - `ReasoningReceipt.assay` (optional) — the reserved receipt landing spot for the Assay: a render-time, run-level epistemic summary that references its run's `ReasoningAssay` records by URN (`assayRefs`) and surfaces the projected `overallState`, weakest-link `binding`, verifier `calibrationRef`, per-state `counts`, and `projectedAt`. A cache of the referenced verdicts, not authoritative over them — re-projectable when an `AssayStandard` improves. Backward-compatible (optional field). The assay's `authority` axis continues to mirror `EventEnvelope.actor`/`integrity`, so the envelope needs no structural change. diff --git a/Makefile b/Makefile index d07b461..531a424 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,16 @@ -.PHONY: validate validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples +.PHONY: validate validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples validate-knowledge-nugget-examples validate-semantic-action-examples -validate: validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples +validate: validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples validate-knowledge-nugget-examples validate-semantic-action-examples @echo "OK: validate" +validate-knowledge-nugget-examples: + python3 -m pip install --user jsonschema >/dev/null + python3 tools/validate_knowledge_nugget_examples.py + +validate-semantic-action-examples: + python3 -m pip install --user jsonschema >/dev/null + python3 tools/validate_semantic_action_examples.py + validate-mpcc-event-examples: python3 -m pip install --user jsonschema >/dev/null python3 tools/validate_mpcc_event_examples.py diff --git a/examples/knowledge_nugget.json b/examples/knowledge_nugget.json new file mode 100644 index 0000000..05c0877 --- /dev/null +++ b/examples/knowledge_nugget.json @@ -0,0 +1,49 @@ +{ + "id": "urn:srcos:knowledge-nugget:gyg_fy2025_network_sales_quote_0001", + "type": "KnowledgeNugget", + "specVersion": "0.1.0", + "sourceRef": { + "docRef": "urn:srcos:dataset:gyg_asx_fy2025_annual_report", + "span": { + "start": 48210, + "end": 48323, + "page": 74 + }, + "contentHash": "sha256-0d863a1cca056ffe7948415a29d795c2581fbfcda7e3e30f544cd078333d1337" + }, + "warrant": { + "type": "direct-quote", + "evidence": [ + "urn:srcos:prov:ifm_extract_run_000412" + ], + "confidence": 0.99 + }, + "text": "Network sales grew 22.6% to $1,138.9 million, with comparable sales growth of 9.4% across the Australian segment.", + "kkoTypeRefs": [ + "http://kbpedia.org/kko/rc/Business", + "https://schemas.srcos.ai/ont/ifm/FilingMetricObservation" + ], + "canonicalPayload": { + "metric": "network_sales", + "value": 1138900000, + "unit": "AUD", + "growthPct": 22.6, + "normalizationRegime": "ifm-metric-v1" + }, + "provenance": [ + { + "rel": "derived_from", + "ref": "urn:srcos:dataset:gyg_asx_fy2025_annual_report" + }, + { + "rel": "extracted_by", + "ref": "urn:srcos:run:ifm_extract_run_000412" + } + ], + "policyLabels": [ + "source:public-filing" + ], + "createdBy": "urn:srcos:agent:ifm_extractor_v2", + "wallTime": "2026-07-29T09:15:00.000Z", + "logicalTime": 4021 +} diff --git a/examples/knowledge_nugget.model_generated.json b/examples/knowledge_nugget.model_generated.json new file mode 100644 index 0000000..e8136c5 --- /dev/null +++ b/examples/knowledge_nugget.model_generated.json @@ -0,0 +1,39 @@ +{ + "id": "urn:srcos:knowledge-nugget:gyg_fy2026_outlook_synthesis_0007", + "type": "KnowledgeNugget", + "specVersion": "0.1.0", + "sourceRef": { + "docRef": "urn:srcos:dataset:gyg_asx_fy2025_annual_report", + "span": { + "start": 46800, + "end": 52400, + "page": 73 + }, + "contentHash": "sha256-b8b7fefebb4e8f612ea0a89fd6f465a9c0e57a09630ce7c565e9c5de86a7fc92" + }, + "warrant": { + "type": "model-generated", + "evidence": [], + "confidence": 0.62 + }, + "text": "Management appears to expect continued store rollout to sustain double-digit network sales growth into FY2026.", + "kkoTypeRefs": [ + "http://kbpedia.org/kko/rc/Business" + ], + "provenance": [ + { + "rel": "conditioned_on", + "ref": "urn:srcos:dataset:gyg_asx_fy2025_annual_report" + }, + { + "rel": "generated_by", + "ref": "urn:srcos:run:ifm_synthesis_run_000413" + } + ], + "policyLabels": [ + "source:public-filing" + ], + "createdBy": "urn:srcos:agent:ifm_synthesizer_v1", + "wallTime": "2026-07-29T09:16:30.000Z", + "logicalTime": 4022 +} diff --git a/examples/semantic_action.effect_request.json b/examples/semantic_action.effect_request.json new file mode 100644 index 0000000..9386233 --- /dev/null +++ b/examples/semantic_action.effect_request.json @@ -0,0 +1,38 @@ +{ + "id": "urn:srcos:semantic-action:trading_place_order_1.2.0", + "type": "SemanticAction", + "specVersion": "0.1.0", + "name": "trading.place_order", + "version": "1.2.0", + "inputs": [ + { + "name": "instrument", + "typeRef": "http://kbpedia.org/kko/rc/FinancialInstrument", + "required": true, + "cardinality": "one" + }, + { + "name": "orderParameters", + "typeRef": "https://schemas.srcos.ai/ont/trading/OrderParameters", + "required": true, + "cardinality": "one" + } + ], + "output": { + "typeRef": "https://schemas.srcos.ai/v2/EffectRequest.json", + "cardinality": "one" + }, + "constraints": [ + { + "kind": "subClassOf", + "subject": "output", + "typeRef": "https://schemas.srcos.ai/v2/EffectRequest.json" + } + ], + "executorRef": "urn:srcos:skill:trading_order_gateway_adapter", + "sideEffects": "effect-request", + "registry": { + "owner": "urn:srcos:party:desk_alpha_ops", + "deprecated": false + } +} diff --git a/examples/semantic_action.json b/examples/semantic_action.json new file mode 100644 index 0000000..2979d06 --- /dev/null +++ b/examples/semantic_action.json @@ -0,0 +1,44 @@ +{ + "id": "urn:srcos:semantic-action:kb_lookup_filing_metric_1.0.0", + "type": "SemanticAction", + "specVersion": "0.1.0", + "name": "kb.lookup_filing_metric", + "version": "1.0.0", + "inputs": [ + { + "name": "company", + "typeRef": "http://kbpedia.org/kko/rc/Business", + "required": true, + "cardinality": "one" + }, + { + "name": "metric", + "typeRef": "https://schemas.srcos.ai/ont/ifm/FilingMetricKind", + "required": true, + "cardinality": "one" + }, + { + "name": "period", + "typeRef": "https://schemas.srcos.ai/ont/ifm/FiscalPeriod", + "required": false, + "cardinality": "one" + } + ], + "output": { + "typeRef": "https://schemas.srcos.ai/ont/ifm/FilingMetricObservation", + "cardinality": "many" + }, + "constraints": [ + { + "kind": "subClassOf", + "subject": "company", + "typeRef": "http://kbpedia.org/kko/rc/Organization" + } + ], + "executorRef": "urn:srcos:skill:ifm_filing_metric_lookup", + "sideEffects": "none", + "registry": { + "owner": "urn:srcos:party:ifm_platform_team", + "deprecated": false + } +} diff --git a/fixtures/knowledge-nugget/conformance.json b/fixtures/knowledge-nugget/conformance.json new file mode 100644 index 0000000..7e9bde5 --- /dev/null +++ b/fixtures/knowledge-nugget/conformance.json @@ -0,0 +1,106 @@ +{ + "description": "Negative conformance vectors for the KnowledgeNugget contract v0.1. Each document MUST FAIL validation against its schema for the stated reason; tools/validate_knowledge_nugget_examples.py enforces this. These vectors keep the content-grain bar executable: closed objects, pinned contract version, a closed warrant taxonomy, content-addressed sources, and the derivation-grounding invariant (computed/inferred warrants must cite evidence).", + "cases": [ + { + "schema": "KnowledgeNugget.json", + "reason": "Unknown top-level property must be rejected (additionalProperties: false).", + "document": { + "id": "urn:srcos:knowledge-nugget:neg_unknown_prop", + "type": "KnowledgeNugget", + "specVersion": "0.1.0", + "sourceRef": { + "docRef": "urn:srcos:dataset:gyg_asx_fy2025_annual_report", + "span": { "start": 10, "end": 20 }, + "contentHash": "sha256-0d863a1cca056ffe7948415a29d795c2581fbfcda7e3e30f544cd078333d1337" + }, + "warrant": { "type": "direct-quote", "evidence": [], "confidence": 0.9 }, + "text": "spanselect", + "policyLabels": [], + "createdBy": "urn:srcos:agent:ifm_extractor_v2", + "wallTime": "2026-07-29T09:15:00.000Z", + "logicalTime": 4021, + "vibeScore": 11 + } + }, + { + "schema": "KnowledgeNugget.json", + "reason": "The warrant taxonomy is closed: warrant types outside the four-kind enum must fail.", + "document": { + "id": "urn:srcos:knowledge-nugget:neg_unknown_warrant_kind", + "type": "KnowledgeNugget", + "specVersion": "0.1.0", + "sourceRef": { + "docRef": "urn:srcos:dataset:gyg_asx_fy2025_annual_report", + "span": { "start": 10, "end": 20 }, + "contentHash": "sha256-0d863a1cca056ffe7948415a29d795c2581fbfcda7e3e30f544cd078333d1337" + }, + "warrant": { "type": "hearsay", "evidence": [], "confidence": 0.5 }, + "text": "spanselect", + "policyLabels": [], + "createdBy": "urn:srcos:agent:ifm_extractor_v2", + "wallTime": "2026-07-29T09:15:00.000Z", + "logicalTime": 4021 + } + }, + { + "schema": "KnowledgeNugget.json", + "reason": "Derivation grounding: an inferred warrant with empty evidence must fail (computed/inferred require at least one evidence ref).", + "document": { + "id": "urn:srcos:knowledge-nugget:neg_ungrounded_inference", + "type": "KnowledgeNugget", + "specVersion": "0.1.0", + "sourceRef": { + "docRef": "urn:srcos:dataset:gyg_asx_fy2025_annual_report", + "span": { "start": 10, "end": 20 }, + "contentHash": "sha256-0d863a1cca056ffe7948415a29d795c2581fbfcda7e3e30f544cd078333d1337" + }, + "warrant": { "type": "inferred", "evidence": [], "confidence": 0.8 }, + "text": "Segment growth implies store-level margins expanded.", + "policyLabels": [], + "createdBy": "urn:srcos:agent:ifm_extractor_v2", + "wallTime": "2026-07-29T09:15:00.000Z", + "logicalTime": 4021 + } + }, + { + "schema": "KnowledgeNugget.json", + "reason": "Content addressing is sha256-only: a non-sha256 contentHash must fail the anchored pattern.", + "document": { + "id": "urn:srcos:knowledge-nugget:neg_weak_content_hash", + "type": "KnowledgeNugget", + "specVersion": "0.1.0", + "sourceRef": { + "docRef": "urn:srcos:dataset:gyg_asx_fy2025_annual_report", + "span": { "start": 10, "end": 20 }, + "contentHash": "md5-d41d8cd98f00b204e9800998ecf8427e" + }, + "warrant": { "type": "direct-quote", "evidence": [], "confidence": 0.9 }, + "text": "spanselect", + "policyLabels": [], + "createdBy": "urn:srcos:agent:ifm_extractor_v2", + "wallTime": "2026-07-29T09:15:00.000Z", + "logicalTime": 4021 + } + }, + { + "schema": "KnowledgeNugget.json", + "reason": "specVersion is pinned: any value other than the 0.1.0 const must fail.", + "document": { + "id": "urn:srcos:knowledge-nugget:neg_wrong_spec_version", + "type": "KnowledgeNugget", + "specVersion": "0.2.0", + "sourceRef": { + "docRef": "urn:srcos:dataset:gyg_asx_fy2025_annual_report", + "span": { "start": 10, "end": 20 }, + "contentHash": "sha256-0d863a1cca056ffe7948415a29d795c2581fbfcda7e3e30f544cd078333d1337" + }, + "warrant": { "type": "direct-quote", "evidence": [], "confidence": 0.9 }, + "text": "spanselect", + "policyLabels": [], + "createdBy": "urn:srcos:agent:ifm_extractor_v2", + "wallTime": "2026-07-29T09:15:00.000Z", + "logicalTime": 4021 + } + } + ] +} diff --git a/fixtures/semantic-action/conformance.json b/fixtures/semantic-action/conformance.json new file mode 100644 index 0000000..5ed4945 --- /dev/null +++ b/fixtures/semantic-action/conformance.json @@ -0,0 +1,99 @@ +{ + "description": "Negative conformance vectors for the SemanticAction contract v0.1. Each document MUST FAIL validation against its schema for the stated reason; tools/validate_semantic_action_examples.py enforces this. These vectors keep the registry bar executable: closed objects, pinned contract version, a closed effect-posture vocabulary with no direct-mutation value, a closed constraint-kind taxonomy, and fully declared input signatures.", + "cases": [ + { + "schema": "SemanticAction.json", + "reason": "Unknown top-level property must be rejected (additionalProperties: false).", + "document": { + "id": "urn:srcos:semantic-action:neg_unknown_prop", + "type": "SemanticAction", + "specVersion": "0.1.0", + "name": "kb.lookup_filing_metric", + "version": "1.0.0", + "inputs": [], + "output": { "typeRef": "https://schemas.srcos.ai/ont/ifm/FilingMetricObservation", "cardinality": "many" }, + "constraints": [], + "executorRef": "urn:srcos:skill:ifm_filing_metric_lookup", + "sideEffects": "none", + "registry": { "owner": "urn:srcos:party:ifm_platform_team", "deprecated": false }, + "coolness": "maximal" + } + }, + { + "schema": "SemanticAction.json", + "reason": "Search-time purity: there is no \"direct\" effect posture — world mutation outside the EffectRequest → EffectDecision lifecycle is unregistrable.", + "document": { + "id": "urn:srcos:semantic-action:neg_direct_mutation", + "type": "SemanticAction", + "specVersion": "0.1.0", + "name": "trading.place_order", + "version": "1.2.0", + "inputs": [ + { "name": "instrument", "typeRef": "http://kbpedia.org/kko/rc/FinancialInstrument", "required": true, "cardinality": "one" } + ], + "output": { "typeRef": "https://schemas.srcos.ai/v2/EffectRequest.json", "cardinality": "one" }, + "constraints": [], + "executorRef": "urn:srcos:skill:trading_order_gateway_adapter", + "sideEffects": "direct", + "registry": { "owner": "urn:srcos:party:desk_alpha_ops", "deprecated": false } + } + }, + { + "schema": "SemanticAction.json", + "reason": "The constraint-kind taxonomy is closed: kinds outside subClassOf/instanceOf/sameAs must fail.", + "document": { + "id": "urn:srcos:semantic-action:neg_unknown_constraint_kind", + "type": "SemanticAction", + "specVersion": "0.1.0", + "name": "kb.lookup_filing_metric", + "version": "1.0.0", + "inputs": [ + { "name": "company", "typeRef": "http://kbpedia.org/kko/rc/Business", "required": true, "cardinality": "one" } + ], + "output": { "typeRef": "https://schemas.srcos.ai/ont/ifm/FilingMetricObservation", "cardinality": "many" }, + "constraints": [ + { "kind": "broaderThan", "subject": "company", "typeRef": "http://kbpedia.org/kko/rc/Organization" } + ], + "executorRef": "urn:srcos:skill:ifm_filing_metric_lookup", + "sideEffects": "none", + "registry": { "owner": "urn:srcos:party:ifm_platform_team", "deprecated": false } + } + }, + { + "schema": "SemanticAction.json", + "reason": "Signature completeness: an input slot missing cardinality must fail — planners need fully declared signatures.", + "document": { + "id": "urn:srcos:semantic-action:neg_partial_input", + "type": "SemanticAction", + "specVersion": "0.1.0", + "name": "kb.lookup_filing_metric", + "version": "1.0.0", + "inputs": [ + { "name": "company", "typeRef": "http://kbpedia.org/kko/rc/Business", "required": true } + ], + "output": { "typeRef": "https://schemas.srcos.ai/ont/ifm/FilingMetricObservation", "cardinality": "many" }, + "constraints": [], + "executorRef": "urn:srcos:skill:ifm_filing_metric_lookup", + "sideEffects": "none", + "registry": { "owner": "urn:srcos:party:ifm_platform_team", "deprecated": false } + } + }, + { + "schema": "SemanticAction.json", + "reason": "Registry posture must be explicit: registry missing the deprecated flag must fail.", + "document": { + "id": "urn:srcos:semantic-action:neg_partial_registry", + "type": "SemanticAction", + "specVersion": "0.1.0", + "name": "kb.lookup_filing_metric", + "version": "1.0.0", + "inputs": [], + "output": { "typeRef": "https://schemas.srcos.ai/ont/ifm/FilingMetricObservation", "cardinality": "many" }, + "constraints": [], + "executorRef": "urn:srcos:skill:ifm_filing_metric_lookup", + "sideEffects": "none", + "registry": { "owner": "urn:srcos:party:ifm_platform_team" } + } + } + ] +} diff --git a/schemas/KnowledgeNugget.json b/schemas/KnowledgeNugget.json new file mode 100644 index 0000000..7fc3d32 --- /dev/null +++ b/schemas/KnowledgeNugget.json @@ -0,0 +1,231 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/v2/KnowledgeNugget.json", + "title": "KnowledgeNugget", + "description": "The estate's L2 content grain: one warrant-typed fragment of knowledge lifted from a governed source, generalizing the production IFM warrant-typed extraction (document → typed values) into a universal contract. Every nugget states WHERE it came from (sourceRef: document URN + span + content hash), HOW it is warranted (direct-quote | computed | inferred | model-generated, with evidence refs and confidence), and WHAT it says (text, plus optional canonicalPayload and ontology type refs). Design rule (normative): warrant.type = model-generated MUST remain visibly distinguishable on every downstream surface — retrieval, ranking, rendering, and admissibility weighting all discount model-generated nuggets relative to source-warranted ones, and no downstream transform may launder a model-generated nugget into a source-warranted one. The time vocabulary (wallTime, logicalTime) is carried verbatim from the MPCC ConversationEvent envelope; parity is machine-enforced by tools/validate_knowledge_nugget_examples.py.", + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "type", + "specVersion", + "sourceRef", + "warrant", + "text", + "createdBy", + "policyLabels", + "wallTime", + "logicalTime" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^urn:srcos:knowledge-nugget:[A-Za-z0-9._~-]+$", + "description": "Stable URN identifier of the nugget. Pattern: urn:srcos:knowledge-nugget:. Invariant: nugget identity is stable and never reused; supersession is expressed through provenance links, never by rewriting an existing id." + }, + "type": { + "const": "KnowledgeNugget", + "description": "Discriminator constant — always \"KnowledgeNugget\"." + }, + "specVersion": { + "const": "0.1.0", + "description": "KnowledgeNugget contract version, pinned per the policy-integrity tranche-0001 discipline. This family is versioned independently of the v2 metadata-plane schemas; changing this const is a contract change." + }, + "sourceRef": { + "type": "object", + "additionalProperties": false, + "required": [ + "docRef", + "span", + "contentHash" + ], + "properties": { + "docRef": { + "type": "string", + "pattern": "^urn:srcos:[a-z0-9-]+:[A-Za-z0-9._~-]+$", + "description": "URN of the governed source document or corpus object this nugget was lifted from (e.g. a Dataset, ArtifactManifest, or content-addressed document object)." + }, + "span": { + "type": "object", + "additionalProperties": false, + "required": [ + "start", + "end" + ], + "properties": { + "start": { + "type": "integer", + "minimum": 0, + "description": "0-based inclusive character offset of the span start within the hashed source text." + }, + "end": { + "type": "integer", + "minimum": 0, + "description": "0-based exclusive character offset of the span end within the hashed source text. Invariant: end >= start; for warrant.type = direct-quote, end - start must equal the length of text (both enforced by the family validator across examples)." + }, + "page": { + "type": "integer", + "minimum": 1, + "description": "Optional 1-based page number of the span within the source document, for paginated sources." + } + }, + "description": "The character span within the hashed source text that this nugget was lifted from. For warrant.type = model-generated this is the conditioning window the generation was given — it does NOT warrant the text (that is the point of the warrant type)." + }, + "contentHash": { + "type": "string", + "pattern": "^sha256-[0-9a-f]{64}$", + "description": "Content hash of the exact source text the span offsets index into, in the form sha256-<64 lowercase hex>. Pins the nugget to an immutable source state so span offsets can never silently drift." + } + }, + "description": "Where this nugget came from: source document URN, character span, and the content hash of the source state the span was read from. Required for every warrant type — even a model-generated nugget must pin the source context it was conditioned on." + }, + "warrant": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "evidence", + "confidence" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "direct-quote", + "computed", + "inferred", + "model-generated" + ], + "description": "How this nugget's text is warranted. direct-quote: text is the exact source span, warranted by sourceRef itself. computed: text/payload derived by deterministic computation over cited source values. inferred: text follows by stated inference from cited premises. model-generated: text was produced by a model conditioned on the source window and is NOT warranted by it. Normative: model-generated MUST be visibly distinguishable on every downstream surface (admissibility discounting); the taxonomy is closed at v0.1 and widening it is a contract bump." + }, + "evidence": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "description": "A stable reference (URN recommended; run- or step-scoped references permitted in v0.1) to evidence grounding this warrant — e.g. a ProvenanceRecord, RunRecord, ReasoningEvent, or a prior KnowledgeNugget." + }, + "uniqueItems": true, + "default": [], + "description": "Evidence references grounding this warrant. Invariant (schema-enforced): computed and inferred warrants must cite at least one evidence ref — a derivation with no cited inputs is not a derivation. direct-quote is grounded by sourceRef itself; model-generated may be evidence-free, which is exactly why it is admissibility-discounted." + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Producer-stated confidence in this warrant, 0..1. Not admissibility: downstream admissibility weighting is a function of warrant.type first (model-generated is discounted regardless of stated confidence) and confidence second." + } + }, + "allOf": [ + { + "if": { + "properties": { + "type": { + "enum": [ + "computed", + "inferred" + ] + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "evidence": { + "minItems": 1 + } + } + } + } + ], + "description": "The warrant typing how this nugget's content is justified: warrant kind, grounding evidence refs, and producer confidence. All three sub-fields are required so the epistemic status of a nugget is never ambiguous." + }, + "text": { + "type": "string", + "minLength": 1, + "description": "The human-readable content of the nugget. For warrant.type = direct-quote this MUST be the exact source span text; for computed/inferred/model-generated it is the produced statement." + }, + "kkoTypeRefs": { + "type": "array", + "items": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "An ontology concept URI (KKO reference concepts recommended, e.g. http://kbpedia.org/kko/rc/...)." + }, + "uniqueItems": true, + "default": [], + "description": "Ontology concept URIs typing this nugget's content, so typed retrieval and the plan compiler can bind nuggets by type (SemanticAction inputs/outputs use the same URI vocabulary)." + }, + "canonicalPayload": { + "type": [ + "object", + "array", + "string", + "number", + "boolean", + "null" + ], + "description": "The normalized, machine-readable form of this nugget's content (e.g. a value-with-unit record for a computed metric, a typed row for an extracted table entry). Invariant: the canonical form must declare the normalization regime used, and any lossy derivation from text must carry provenance links." + }, + "provenance": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "rel", + "ref" + ], + "properties": { + "rel": { + "type": "string", + "minLength": 1, + "description": "Relation of the linked artifact to this nugget (e.g. derived_from, extracted_by, supersedes)." + }, + "ref": { + "type": "string", + "minLength": 1, + "description": "URN or stable reference of the linked artifact." + } + }, + "description": "A single provenance link (relation + reference)." + }, + "uniqueItems": true, + "default": [], + "description": "Typed provenance chain links from this nugget to upstream artifacts: the extraction run, the parent document, prior nuggets it supersedes, and any ProvenanceRecord that carries the full W3C PROV chain. Same link shape as the MPCC envelope's provenanceLinks." + }, + "policyLabels": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "description": "A policy label." + }, + "uniqueItems": true, + "default": [], + "description": "Policy labels attached to this nugget by producers or the policy fabric. Required (empty allowed) so producers state the policy posture explicitly, per the tranche-0001 bar." + }, + "createdBy": { + "type": "string", + "minLength": 1, + "description": "Authority reference of the actor that created this nugget (human subject, agent, service, or system). Subject or Agent Registry URNs are recommended; free-form identifiers are permitted in v0.1." + }, + "wallTime": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 wall-clock time of the event as observed by the producer. Family profiles map their primary domain timestamp onto this field." + }, + "logicalTime": { + "type": [ + "integer", + "string" + ], + "minimum": 0, + "minLength": 1, + "description": "Producer-scoped logical clock: a non-negative integer for scalar (Lamport-style) clocks, or a non-empty string for encoded vector/hybrid clocks. Invariant: causalParents must never point forward in logical time." + } + } +} diff --git a/schemas/README.md b/schemas/README.md index 3dc5e15..456764c 100644 --- a/schemas/README.md +++ b/schemas/README.md @@ -5,6 +5,24 @@ This directory contains the JSON Schema (draft 2020-12) files that make up the S --- +## Recent additions — Knowledge Nugget + Semantic Action registry v0.1 + +The L2 content-grain and typed-action-registry contracts add the following top-level schemas: + +| File | Type | URN prefix | +|------|------|-----------| +| `KnowledgeNugget.json` | KnowledgeNugget | `urn:srcos:knowledge-nugget:` | +| `SemanticAction.json` | SemanticAction | `urn:srcos:semantic-action:` | + +These types support: +- the estate's L2 content grain: warrant-typed knowledge fragments (`direct-quote` / `computed` / `inferred` / `model-generated`) with content-addressed source spans (`sha256-` pinned), evidence refs, confidence, ontology type refs, and provenance chain links — generalizing the production IFM warrant-typed extraction. Normative: `model-generated` MUST stay visibly distinguishable downstream (admissibility discounting); `computed`/`inferred` must cite evidence (schema-enforced) +- the declarative typed-action registry for the NL→plan compiler: ontology-typed inputs/output, `subClassOf`/`instanceOf`/`sameAs` constraints (polymorphism via subsumption), executor binding, and a two-value effect posture (`none` | `effect-request`) — actions are side-effect-free at plan-search time, and world-changing execution defers to the MPCC `EffectRequest` → `EffectDecision` lifecycle (no direct-mutation vocabulary exists) +- `KnowledgeNugget.wallTime`/`logicalTime` carried verbatim from the `ConversationEvent` envelope, and `SemanticAction` slot types sharing the `kkoTypeRefs` URI vocabulary, so content grains bind directly as typed planner values + +Validation: `make validate-knowledge-nugget-examples` and `make validate-semantic-action-examples` (envelope parity, warrant soundness, binding/purity invariants, and negative vectors under `fixtures/knowledge-nugget/` and `fixtures/semantic-action/`). Normative notes: `specs/knowledge-nugget-contract.md`, `specs/semantic-action-contract.md`. + +--- + ## Recent additions — MPCC Event Contract v0.1 (conversation + trading event family) The MPCC (multi-party conversation control) event contract adds the following top-level schemas: diff --git a/schemas/SemanticAction.json b/schemas/SemanticAction.json new file mode 100644 index 0000000..e9af497 --- /dev/null +++ b/schemas/SemanticAction.json @@ -0,0 +1,184 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/v2/SemanticAction.json", + "title": "SemanticAction", + "description": "A declarative, typed action registration for the NL→plan compiler: the planner discovers, instantiates, and composes actions by TYPE — ontology URIs over inputs and output, with subsumption constraints — never by executing them. Polymorphism works via subClassOf: a value is bindable to a slot when its type is a subclass of the slot's typeRef, and constraints add further subsumption requirements. Normative purity rule (search-time purity): actions are side-effect-free at plan-search time — search may bind and score an action with zero world change. Any world-changing execution MUST go through the MPCC effect lifecycle (EffectRequest → EffectDecision → EffectRecord; decision before action): sideEffects = \"effect-request\" declares that the registered executor emits an EffectRequest and defers to an EffectDecision rather than acting directly; sideEffects = \"none\" declares a pure lookup/computation. There is deliberately no vocabulary for direct world mutation.", + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "type", + "specVersion", + "name", + "version", + "inputs", + "output", + "constraints", + "executorRef", + "sideEffects", + "registry" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^urn:srcos:semantic-action:[A-Za-z0-9._~-]+$", + "description": "Stable URN identifier of this action registration. Pattern: urn:srcos:semantic-action:. Invariant: registration identity is stable and never reused; a changed signature is a new version, not a rewritten registration." + }, + "type": { + "const": "SemanticAction", + "description": "Discriminator constant — always \"SemanticAction\"." + }, + "specVersion": { + "const": "0.1.0", + "description": "SemanticAction contract version, pinned per the policy-integrity tranche-0001 discipline. This family is versioned independently of the v2 metadata-plane schemas; changing this const is a contract change." + }, + "name": { + "type": "string", + "pattern": "^[a-z][a-z0-9_.]*$", + "description": "Stable machine name of the action within the registry (dot-namespaced lowercase, e.g. kb.lookup_filing_metric). Names are the human/registry handle; planners bind by id." + }, + "version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$", + "description": "Semantic version of THIS action's signature (inputs, output, constraints, effect posture), independent of the family specVersion const. Any signature change bumps this version." + }, + "inputs": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "typeRef", + "required", + "cardinality" + ], + "properties": { + "name": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9_]*$", + "description": "Bindable input name, unique within this action. Constraint subjects reference this name." + }, + "typeRef": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "Ontology concept URI (KKO reference concepts recommended) typing the values bindable to this input. Binding rule: a value binds when its type is a subclass of (or equal to) this concept." + }, + "required": { + "type": "boolean", + "description": "Whether the planner must bind this input for the action to be applicable." + }, + "cardinality": { + "type": "string", + "enum": [ + "one", + "many" + ], + "description": "Whether this input binds exactly one value (one) or a set of values (many)." + } + }, + "description": "One typed input slot of the action." + }, + "uniqueItems": true, + "default": [], + "description": "The action's typed input slots. Required (empty allowed — generator actions may take no inputs) so the signature is always fully declared. Invariant: input names are unique within an action (enforced by the family validator across examples)." + }, + "output": { + "type": "object", + "additionalProperties": false, + "required": [ + "typeRef", + "cardinality" + ], + "properties": { + "typeRef": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "Ontology concept URI typing the action's output. For sideEffects = \"effect-request\" actions this is the proposal the executor emits (e.g. the EffectRequest contract URI), never the world state it might eventually change." + }, + "cardinality": { + "type": "string", + "enum": [ + "one", + "many" + ], + "description": "Whether the action produces exactly one value (one) or a set of values (many)." + } + }, + "description": "The action's typed output. Exactly one output per action at v0.1; multi-output actions decompose into multiple registrations." + }, + "constraints": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "subject", + "typeRef" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "subClassOf", + "instanceOf", + "sameAs" + ], + "description": "Constraint kind. subClassOf: the subject's type must be a subclass of typeRef — this is how polymorphism works at plan-search time. instanceOf: the bound value must be an instance of typeRef. sameAs: the subject must denote the same ontology individual as typeRef. The kind taxonomy is closed at v0.1; widening it is a contract bump." + }, + "subject": { + "type": "string", + "minLength": 1, + "description": "The input name this constraint applies to, or the literal string \"output\" for the action's output. Resolution against declared input names is enforced by the family validator." + }, + "typeRef": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "Ontology concept or individual URI the constraint holds against." + } + }, + "description": "One typed applicability constraint, checked at plan-search time against the ontology — no execution involved." + }, + "uniqueItems": true, + "default": [], + "description": "Typed applicability constraints evaluated at plan-search time. Required (empty allowed) so producers state the constraint posture explicitly, per the tranche-0001 bar." + }, + "executorRef": { + "type": "string", + "pattern": "^urn:srcos:[a-z0-9-]+:[A-Za-z0-9._~-]+$", + "description": "URN of the executor bound to this action (e.g. a SkillManifest, Connector, or WorkloadSpec). The registration is declarative; the executor is where the declared sideEffects posture is realized and audited." + }, + "sideEffects": { + "type": "string", + "enum": [ + "none", + "effect-request" + ], + "description": "Effect posture of the registered executor. Actions are side-effect-free at plan-search time in both cases. \"none\": a pure lookup/computation — safe to execute without governance hops. \"effect-request\": the executor does NOT change the world itself; it emits an EffectRequest, and any actual change happens only after an EffectDecision approves it, recorded as an EffectRecord (decision before action). There is deliberately no \"direct\" value: an action that mutates the world without the effect lifecycle is unregistrable." + }, + "registry": { + "type": "object", + "additionalProperties": false, + "required": [ + "owner", + "deprecated" + ], + "properties": { + "owner": { + "type": "string", + "minLength": 1, + "description": "Authority reference of the actor accountable for this registration (human subject, team, agent, or service). Subject or Agent Registry URNs are recommended; free-form identifiers are permitted in v0.1." + }, + "deprecated": { + "type": "boolean", + "description": "Whether this registration is deprecated. Deprecated actions remain resolvable for replaying old plans but MUST NOT be offered to new plan searches." + } + }, + "description": "Registry metadata: the accountable owner and the deprecation flag. Both required so ownership and lifecycle state are never ambiguous." + } + } +} diff --git a/specs/knowledge-nugget-contract.md b/specs/knowledge-nugget-contract.md new file mode 100644 index 0000000..2f3bfe6 --- /dev/null +++ b/specs/knowledge-nugget-contract.md @@ -0,0 +1,94 @@ +# KnowledgeNugget Contract v0.1 — the estate's L2 content grain + +Status: v0.1.0 (normative for `schemas/KnowledgeNugget.json`) +Provenance: the production IFM warrant-typed extraction (document → typed +values), generalized into a universal content-grain contract. +Validation: `make validate-knowledge-nugget-examples` +(tools/validate_knowledge_nugget_examples.py). + +## 1. Scope + +A `KnowledgeNugget` is one warrant-typed fragment of knowledge lifted from a +governed source. It is the L2 content grain: the smallest unit that retrieval, +ranking, planning, and rendering surfaces exchange. Every nugget answers three +questions, all machine-checkable: + +- **WHERE** — `sourceRef`: source document URN + character span + the + `sha256-<64hex>` content hash of the source state the span was read from, so + offsets can never silently drift. +- **HOW** — `warrant`: one of `direct-quote | computed | inferred | + model-generated`, plus evidence refs and producer confidence. +- **WHAT** — `text`, optional `canonicalPayload` (normalized machine form), + and optional `kkoTypeRefs` (ontology concept URIs for typed retrieval and + planner binding — the same URI vocabulary `SemanticAction` types its slots + with). + +## 2. Warrant taxonomy and admissibility (normative) + +The four-kind warrant taxonomy is closed at v0.1 (enum-enforced); widening it +is a minor contract bump. + +1. `direct-quote` — text IS the exact source span; warranted by `sourceRef` + itself. Exactness is executable: `span.end - span.start` must equal the + length of `text` (validator-enforced across examples). +2. `computed` — derived by deterministic computation over cited source values. + Must cite at least one evidence ref (schema-enforced via `if/then`): a + derivation with no cited inputs is not a derivation. +3. `inferred` — follows by stated inference from cited premises. Same + evidence-grounding rule as `computed`. +4. `model-generated` — produced by a model conditioned on the source window; + NOT warranted by it. May be evidence-free — which is exactly why it is + discounted. + +**Design rule (normative): `warrant.type = model-generated` MUST remain +visibly distinguishable on every downstream surface.** Retrieval, ranking, +rendering, and admissibility weighting all discount model-generated nuggets +relative to source-warranted ones, and no downstream transform may launder a +model-generated nugget into a source-warranted one. Admissibility is a +function of warrant type first and stated confidence second — a +model-generated nugget at confidence 0.99 still ranks below a direct quote. +The example set must always exercise both poles of this contrast +(validator-enforced). + +`sourceRef` is required for every warrant type: even a model-generated nugget +must pin the source window it was conditioned on. The span then records the +conditioning window, not a warrant. + +## 3. One time vocabulary, not two + +`wallTime` and `logicalTime` are carried **verbatim** from the MPCC +`ConversationEvent` envelope, with deep-equality parity enforced by +`tools/validate_knowledge_nugget_examples.py` — the same discipline the MPCC +trading profiles use. A nugget is not an event, but its creation instant lives +on the same clocks as the conversation fabric that produced it, so nuggets and +events order consistently in provenance chains. + +## 4. Overlap decisions (spec-first conformance) + +| Existing contract | Decision | +|-------------------|----------| +| `ConversationEvent` | A nugget is content, not a communicative act. Extraction runs may emit events whose `provenanceLinks` reference nuggets; the nugget's `provenance` links back. No envelope duplication beyond the parity-enforced time vocabulary. | +| `ProvenanceRecord` | Not duplicated. `provenance` carries typed `{rel, ref}` chain links (same link shape as the MPCC `provenanceLinks`); a full W3C PROV chain lives in `ProvenanceRecord` and is referenced, not embedded. | +| `MemoryEntry` | Agent memory (`rule`/`learned`/`recap`) stays there. Nuggets are source-warranted content grains; a memory may cite nuggets as evidence. | +| `GlossaryTerm` | Definitions of terms stay there. Nuggets carry content about the world, typed by `kkoTypeRefs`. | +| `ContentRef` | Digest-based blob addressing stays there. `sourceRef.contentHash` pins the hashed source text state; a `ContentRef` may be what `docRef` resolves through. | +| `ReasoningAssay` | Claim verdicts stay there. `warrant.evidence` may cite assays/reasoning events as grounding for `inferred` nuggets. | + +## 5. Versioning + +The family versions as one contract, pinned by the `specVersion` const +`0.1.0`. Additive optional fields or widened enums bump the minor; anything +that can invalidate an existing document bumps the major, with CHANGELOG + ADR +per CONTRIBUTING.md. + +## 6. Known gaps (deliberate, v0.1) + +- No OpenAPI/AsyncAPI operations or semantic-context mappings yet (matches how + recent contract families landed; wiring follows once names have settled). +- `docRef` is a generic `urn:srcos:` URN pending a dedicated document-identity + contract; `warrant.evidence` entries are free-form stable references + (URNs recommended, run/step-scoped references permitted). +- Span offsets are character-based over the hashed source text; a + byte-offset/encoding profile for binary sources is future work. +- Supersession (`provenance` rel `supersedes`) is a convention at v0.1, not + yet a typed lifecycle. diff --git a/specs/semantic-action-contract.md b/specs/semantic-action-contract.md new file mode 100644 index 0000000..e895a28 --- /dev/null +++ b/specs/semantic-action-contract.md @@ -0,0 +1,95 @@ +# SemanticAction Contract v0.1 — the declarative typed-action registry + +Status: v0.1.0 (normative for `schemas/SemanticAction.json`) +Provenance: the NL→plan compiler lane — actions registered as typed, +declarative signatures the planner searches over, in the STRIPS tradition of +typed operators rather than executable tool lists. +Validation: `make validate-semantic-action-examples` +(tools/validate_semantic_action_examples.py). + +## 1. Scope + +A `SemanticAction` is a declarative registration: name + version + typed +inputs + typed output + subsumption constraints + executor binding + effect +posture + registry metadata. The NL→plan compiler grounds a natural-language +request into a goal type, then searches the registry for action compositions +whose type signatures chain from what it has to what it needs. Discovery and +composition are **by type only** — the planner never executes anything to find +a plan. + +## 2. Typing and polymorphism (normative) + +- `inputs[].typeRef` / `output.typeRef` are ontology concept URIs (KKO + reference concepts recommended — the same URI vocabulary + `KnowledgeNugget.kkoTypeRefs` uses, so nuggets are directly bindable as + typed planner values). +- **Binding rule: a value binds a slot when its type is a subclass of (or + equal to) the slot's `typeRef`.** This is how polymorphism works: an action + over `kko:Organization` accepts a value typed `kko:Business` without a + per-subtype registration. +- `constraints[]` add further requirements, each checked at plan-search time + against the ontology: `subClassOf` (subject's type specializes `typeRef`), + `instanceOf` (bound value is an instance of `typeRef`), `sameAs` (subject + denotes the same individual as `typeRef`). The kind taxonomy is closed at + v0.1; `subject` is a declared input name or the literal `"output"` + (resolution validator-enforced). +- Input signatures are total: every slot declares `name`, `typeRef`, + `required`, and `cardinality` (`one|many`) — a planner must never meet a + partially declared slot. + +## 3. Search-time purity and B-after-A (normative) + +**Actions are side-effect-free at plan-search time.** Search may instantiate, +bind, and score any action with zero world change. That is why the whole +registry can be searched exhaustively and adversarially without governance +hops. + +`sideEffects` declares the executor's run-time posture, and its vocabulary is +exactly two values (validator-pinned so a third value can never land +silently): + +- `"none"` — a pure lookup/computation; safe to execute without governance + hops. +- `"effect-request"` — the executor does **NOT** change the world itself; it + emits an MPCC `EffectRequest`, and any actual change happens only after an + `EffectDecision` approves it, recorded as an `EffectRecord` (decision before + action — B-after-A). + +There is deliberately **no `"direct"` value**: an action that would mutate the +world without the effect lifecycle is unregistrable, and the negative +conformance vectors keep that executable. The canonical effect-request example +makes the posture structural: its `output.typeRef` is the `EffectRequest` +contract itself — at plan time the action's output IS the proposal, never the +effect. + +## 4. Overlap decisions (spec-first conformance) + +| Existing contract | Decision | +|-------------------|----------| +| `EffectRequest` / `EffectDecision` / `EffectRecord` | Not duplicated. `sideEffects: "effect-request"` defers to that lifecycle by posture; the effect-request example's output is typed by the `EffectRequest` contract URI. | +| `SkillManifest` | Runtime skill activation/requirements stay there. `executorRef` may point at a SkillManifest; `SemanticAction` is the plan-search type surface over it. | +| `LauncherAction` | Workstation launcher/command-bus surface stays there; not a planner registry. | +| `WorkflowSpec` / `WorkflowNode` | A compiled plan may materialize as a `WorkflowSpec`; `SemanticAction` is the search-time vocabulary the compiler draws from, not the executed DAG. | +| `ExecutionDecision` / `PolicyDecision` | Governance verdicts stay there; the effect lifecycle referenced above already grounds in them. | +| `CapabilityContract` / `CapabilityToken` | Authority to *run* an executor is the capability plane's concern; the registry only declares the typed surface and effect posture. | + +## 5. Versioning + +The family versions as one contract, pinned by the `specVersion` const +`0.1.0`. Each registration additionally carries its own semver `version` for +its signature; any signature change bumps it. Additive optional fields or +widened enums bump the family minor; anything that can invalidate an existing +document bumps the major, with CHANGELOG + ADR per CONTRIBUTING.md. + +## 6. Known gaps (deliberate, v0.1) + +- No OpenAPI/AsyncAPI operations or semantic-context mappings yet (matches how + recent contract families landed; wiring follows once names have settled). +- Subsumption checking (`subClassOf` resolution against KKO or another + ontology) happens in the planner, not in CI — CI validates structure and + subject resolution only. +- One output per action; multi-output actions decompose into multiple + registrations. +- Preconditions/postconditions beyond type constraints (guards, cost models, + quality profiles) are future work; `deprecated` is the only lifecycle flag + at v0.1. diff --git a/tools/validate_knowledge_nugget_examples.py b/tools/validate_knowledge_nugget_examples.py new file mode 100644 index 0000000..f93d525 --- /dev/null +++ b/tools/validate_knowledge_nugget_examples.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +"""Validate the KnowledgeNugget contract family (the estate's L2 content grain). + +Five checks, not one: + 1. schema conformance — the schema is a valid draft-2020-12 document and + every canonical example validates against it; + 2. strictness bar — the schema holds the tranche-0001 bar: top-level + "additionalProperties": false, specVersion pinned to the 0.1.0 const, an + anchored urn:srcos: id pattern, and a type const equal to the title; + 3. envelope consistency — the wallTime and logicalTime sub-schemas are + deep-equal to the MPCC ConversationEvent envelope's, so the content grain + can never drift into a second time vocabulary; + 4. warrant soundness — across the example set: span offsets are ordered + (end >= start), a direct-quote span's length equals its text length + (exactness), computed/inferred warrants carry at least one evidence ref, + and the set exercises the admissibility contrast (at least one + direct-quote and at least one model-generated nugget, so downstream + surfaces always have both poles to distinguish); + 5. negative vectors — fixtures/knowledge-nugget/conformance.json all FAIL + for their stated reasons. +""" +from __future__ import annotations + +import json +from pathlib import Path + +import jsonschema + +ROOT = Path(__file__).resolve().parents[1] + +SCHEMA_NAME = "KnowledgeNugget.json" +ENVELOPE_AUTHORITY = "ConversationEvent.json" + +EXAMPLES = [ + "knowledge_nugget.json", + "knowledge_nugget.model_generated.json", +] + +# Time vocabulary carried verbatim from the MPCC ConversationEvent envelope. +TIME_KEYS = ["wallTime", "logicalTime"] + + +def load(path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) + + +def check_conformance(schema: dict, checks: dict[str, bool]) -> None: + jsonschema.validators.validator_for(schema).check_schema(schema) + for example_name in EXAMPLES: + example = load(ROOT / "examples" / example_name) + jsonschema.validate(example, schema) + checks[f"example:{example_name}"] = True + + +def check_strictness(schema: dict, checks: dict[str, bool]) -> None: + if schema.get("additionalProperties") is not False: + raise SystemExit(f"{SCHEMA_NAME}: top-level additionalProperties must be false") + if schema["properties"]["specVersion"].get("const") != "0.1.0": + raise SystemExit(f"{SCHEMA_NAME}: specVersion must be pinned to const 0.1.0") + pattern = schema["properties"]["id"].get("pattern", "") + if not (pattern.startswith("^urn:srcos:") and pattern.endswith("$")): + raise SystemExit(f"{SCHEMA_NAME}: id pattern must be an anchored urn:srcos: pattern") + if schema["properties"]["type"].get("const") != schema["title"]: + raise SystemExit(f"{SCHEMA_NAME}: type const must equal title") + checks[f"strictness:{SCHEMA_NAME}"] = True + + +def check_envelope_consistency(schema: dict, checks: dict[str, bool]) -> None: + authority = load(ROOT / "schemas" / ENVELOPE_AUTHORITY)["properties"] + props = schema["properties"] + for key in TIME_KEYS: + if key not in props: + raise SystemExit(f"{SCHEMA_NAME}: missing shared envelope property {key!r}") + if props[key] != authority[key]: + raise SystemExit( + f"{SCHEMA_NAME}: envelope property {key!r} drifted from " + f"ConversationEvent — one time vocabulary, not a second one" + ) + checks[f"envelope-consistency:{key}"] = True + + +def check_warrant_soundness(checks: dict[str, bool]) -> None: + examples = [load(ROOT / "examples" / name) for name in EXAMPLES] + warrant_types = set() + for name, nugget in zip(EXAMPLES, examples): + span = nugget["sourceRef"]["span"] + assert span["end"] >= span["start"], f"{name}: span.end must be >= span.start" + warrant = nugget["warrant"] + warrant_types.add(warrant["type"]) + if warrant["type"] == "direct-quote": + assert span["end"] - span["start"] == len(nugget["text"]), ( + f"{name}: a direct-quote span must be exactly as long as its text" + ) + if warrant["type"] in ("computed", "inferred"): + assert len(warrant["evidence"]) >= 1, ( + f"{name}: computed/inferred warrants must cite evidence" + ) + assert "direct-quote" in warrant_types, "example set must include a direct-quote nugget" + assert "model-generated" in warrant_types, ( + "example set must include a model-generated nugget — the admissibility " + "contrast must stay exercised" + ) + checks["warrant-soundness:examples"] = True + + +def check_negative_vectors(schema: dict, checks: dict[str, bool]) -> None: + fixture = load(ROOT / "fixtures" / "knowledge-nugget" / "conformance.json") + for i, case in enumerate(fixture["cases"]): + if case["schema"] != SCHEMA_NAME: + raise SystemExit(f"negative vector {i} targets unexpected schema {case['schema']}") + try: + jsonschema.validate(case["document"], schema) + except jsonschema.ValidationError: + checks[f"negative:{i}:{case['schema']}"] = True + continue + raise SystemExit( + f"negative vector {i} ({case['schema']}) unexpectedly PASSED: {case['reason']}" + ) + + +def main() -> int: + schema = load(ROOT / "schemas" / SCHEMA_NAME) + checks: dict[str, bool] = {} + + check_conformance(schema, checks) + check_strictness(schema, checks) + check_envelope_consistency(schema, checks) + check_warrant_soundness(checks) + check_negative_vectors(schema, checks) + + print(json.dumps({"ok": all(checks.values()), "checks": checks}, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/validate_semantic_action_examples.py b/tools/validate_semantic_action_examples.py new file mode 100644 index 0000000..d043d80 --- /dev/null +++ b/tools/validate_semantic_action_examples.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +"""Validate the SemanticAction contract family (the declarative typed-action registry). + +Five checks, not one: + 1. schema conformance — the schema is a valid draft-2020-12 document and + every canonical example validates against it; + 2. strictness bar — the schema holds the tranche-0001 bar: top-level + "additionalProperties": false, specVersion pinned to the 0.1.0 const, an + anchored urn:srcos: id pattern, and a type const equal to the title; + 3. binding soundness — across the example set: input names are unique within + each action, and every constraint subject resolves to a declared input + name or the literal "output" (the planner must never meet a dangling + constraint subject); + 4. purity posture — the schema's sideEffects vocabulary is exactly + {"none", "effect-request"} (no direct-mutation value may ever be added + silently), and the example set exercises both postures, including an + effect-request action whose output is the EffectRequest proposal itself; + 5. negative vectors — fixtures/semantic-action/conformance.json all FAIL + for their stated reasons. +""" +from __future__ import annotations + +import json +from pathlib import Path + +import jsonschema + +ROOT = Path(__file__).resolve().parents[1] + +SCHEMA_NAME = "SemanticAction.json" + +EXAMPLES = [ + "semantic_action.json", + "semantic_action.effect_request.json", +] + +SIDE_EFFECTS_VOCABULARY = ["none", "effect-request"] + + +def load(path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) + + +def check_conformance(schema: dict, checks: dict[str, bool]) -> None: + jsonschema.validators.validator_for(schema).check_schema(schema) + for example_name in EXAMPLES: + example = load(ROOT / "examples" / example_name) + jsonschema.validate(example, schema) + checks[f"example:{example_name}"] = True + + +def check_strictness(schema: dict, checks: dict[str, bool]) -> None: + if schema.get("additionalProperties") is not False: + raise SystemExit(f"{SCHEMA_NAME}: top-level additionalProperties must be false") + if schema["properties"]["specVersion"].get("const") != "0.1.0": + raise SystemExit(f"{SCHEMA_NAME}: specVersion must be pinned to const 0.1.0") + pattern = schema["properties"]["id"].get("pattern", "") + if not (pattern.startswith("^urn:srcos:") and pattern.endswith("$")): + raise SystemExit(f"{SCHEMA_NAME}: id pattern must be an anchored urn:srcos: pattern") + if schema["properties"]["type"].get("const") != schema["title"]: + raise SystemExit(f"{SCHEMA_NAME}: type const must equal title") + checks[f"strictness:{SCHEMA_NAME}"] = True + + +def check_binding_soundness(checks: dict[str, bool]) -> None: + for name in EXAMPLES: + action = load(ROOT / "examples" / name) + input_names = [slot["name"] for slot in action["inputs"]] + assert len(input_names) == len(set(input_names)), ( + f"{name}: input names must be unique within an action" + ) + subjects = set(input_names) | {"output"} + for constraint in action["constraints"]: + assert constraint["subject"] in subjects, ( + f"{name}: constraint subject {constraint['subject']!r} does not " + f"resolve to a declared input name or \"output\"" + ) + checks[f"binding-soundness:{name}"] = True + + +def check_purity_posture(schema: dict, checks: dict[str, bool]) -> None: + vocabulary = schema["properties"]["sideEffects"].get("enum") + if vocabulary != SIDE_EFFECTS_VOCABULARY: + raise SystemExit( + f"{SCHEMA_NAME}: sideEffects vocabulary must be exactly " + f"{SIDE_EFFECTS_VOCABULARY} — no direct-mutation value may be added silently" + ) + postures = {} + for name in EXAMPLES: + action = load(ROOT / "examples" / name) + postures[action["sideEffects"]] = action + assert set(postures) == set(SIDE_EFFECTS_VOCABULARY), ( + "example set must exercise both sideEffects postures" + ) + effect_action = postures["effect-request"] + assert "EffectRequest" in effect_action["output"]["typeRef"], ( + "the effect-request example's output must be the EffectRequest proposal " + "itself — the action proposes, it never acts directly" + ) + checks["purity-posture:vocabulary"] = True + checks["purity-posture:examples"] = True + + +def check_negative_vectors(schema: dict, checks: dict[str, bool]) -> None: + fixture = load(ROOT / "fixtures" / "semantic-action" / "conformance.json") + for i, case in enumerate(fixture["cases"]): + if case["schema"] != SCHEMA_NAME: + raise SystemExit(f"negative vector {i} targets unexpected schema {case['schema']}") + try: + jsonschema.validate(case["document"], schema) + except jsonschema.ValidationError: + checks[f"negative:{i}:{case['schema']}"] = True + continue + raise SystemExit( + f"negative vector {i} ({case['schema']}) unexpectedly PASSED: {case['reason']}" + ) + + +def main() -> int: + schema = load(ROOT / "schemas" / SCHEMA_NAME) + checks: dict[str, bool] = {} + + check_conformance(schema, checks) + check_strictness(schema, checks) + check_binding_soundness(checks) + check_purity_posture(schema, checks) + check_negative_vectors(schema, checks) + + print(json.dumps({"ok": all(checks.values()), "checks": checks}, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())