-
Notifications
You must be signed in to change notification settings - Fork 6
[NRCL-67] Add versioned cross-runtime contract bundle #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
6ad9d9b
feat(contracts): add versioned Neural interoperability bundle
hudsonaikins 941c491
fix(contracts): reject naive verifier clocks
hudsonaikins bcc9934
fix(contracts): enforce semantic integrity
hudsonaikins 6f5da7c
fix(contracts): close schema validation gaps
hudsonaikins 7ed9b51
fix: close cross-runtime semantic gaps
hudsonaikins 6322197
fix: harden contract identity and signing
hudsonaikins 7cd0514
fix: bind reviewed contract outcomes
hudsonaikins 7823eed
fix: validate resolved contract lineage
hudsonaikins ebd205a
refactor: define structural contract boundary
hudsonaikins a8b4ea8
fix: harden structural contract validation
hudsonaikins File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| --- | ||
| title: Cross-runtime contracts | ||
| description: Versioned Neural objects shared with hosted consumers | ||
| --- | ||
|
|
||
| Neural owns the JSON Schema for six versioned objects used across Python and | ||
| TypeScript runtimes: | ||
|
|
||
| - `MarketSnapshot` | ||
| - `ResearchEvidenceRef` | ||
| - `ExecutionIntent` | ||
| - `RiskDecision` | ||
| - `PaperOrder` | ||
| - `PostTradeReview` | ||
|
|
||
| The authoritative bundle is | ||
| `neural/contracts/schemas/neural-contracts-v1.schema.json`. Pydantic validators, | ||
| golden fixtures, and the digest manifest are derived from that source. A | ||
| consumer must pin the manifest digests rather than reinterpret the contract. | ||
|
|
||
| ## Validate an object | ||
|
|
||
| ```python | ||
| from neural.contracts import validate_contract | ||
|
|
||
| validated = validate_contract(candidate) | ||
| ``` | ||
|
|
||
| Validation rejects unknown schema names, unknown versions, extra fields, and | ||
| malformed lineage. Decimal probabilities and prices use strings so canonical | ||
| JSON bytes are stable across runtimes. | ||
|
|
||
| This layer is a structural transport boundary. It validates shape, canonical | ||
| hashes, exact lineage identities, and transport safety. It does not decide | ||
| market coherence, risk approval, order-fill correctness, temporal business | ||
| ordering, redaction policy, or realized PnL. Consumers must run their owning | ||
| domain validator before treating a structurally valid object as actionable. | ||
|
|
||
| ## Verify a signed envelope | ||
|
|
||
| ```python | ||
| from datetime import datetime, timezone | ||
|
|
||
| from neural.contracts import InMemoryNonceReplayGuard, verify_envelope | ||
|
|
||
| contract = verify_envelope( | ||
| envelope, | ||
| secrets={"consumer-key-v1": secret}, | ||
| replay_guard=InMemoryNonceReplayGuard(), | ||
| now=datetime.now(timezone.utc), | ||
| ) | ||
| ``` | ||
|
|
||
| Verification fails closed on schema, payload hash, lifetime, key identity, | ||
| HMAC signature, and nonce replay. Inject a durable replay guard in a hosted | ||
| consumer; the in-memory guard is intended for local and test use. | ||
|
|
||
| Signing does not grant execution authority. Risk policy and order execution | ||
| remain consumer-owned gates. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| { | ||
| "title": "Architecture", | ||
| "pages": ["start-here", "overview", "stability"] | ||
| "pages": ["start-here", "overview", "contracts", "stability"] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| """Versioned cross-runtime contracts for Neural consumers.""" | ||
|
|
||
| from .canonical import ( | ||
| ContractEnvelopeError, | ||
| InMemoryNonceReplayGuard, | ||
| canonical_json, | ||
| contract_payload_hash, | ||
| sign_envelope, | ||
| verify_envelope, | ||
| with_payload_hash, | ||
| ) | ||
| from .registry import ( | ||
| CONTRACT_NAMES, | ||
| CONTRACT_VERSION, | ||
| ContractValidationError, | ||
| ExecutionIntent, | ||
| MarketSnapshot, | ||
| PaperOrder, | ||
| PostTradeReview, | ||
| ResearchEvidenceRef, | ||
| RiskDecision, | ||
| contract_model, | ||
| load_contract_bundle, | ||
| schema_for, | ||
| validate_contract, | ||
| validate_json_schema, | ||
| ) | ||
|
|
||
| __all__ = [ | ||
| "CONTRACT_NAMES", | ||
| "CONTRACT_VERSION", | ||
| "ContractEnvelopeError", | ||
| "ContractValidationError", | ||
| "ExecutionIntent", | ||
| "InMemoryNonceReplayGuard", | ||
| "MarketSnapshot", | ||
| "PaperOrder", | ||
| "PostTradeReview", | ||
| "ResearchEvidenceRef", | ||
| "RiskDecision", | ||
| "canonical_json", | ||
| "contract_model", | ||
| "contract_payload_hash", | ||
| "load_contract_bundle", | ||
| "schema_for", | ||
| "sign_envelope", | ||
| "validate_contract", | ||
| "validate_json_schema", | ||
| "verify_envelope", | ||
| "with_payload_hash", | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| """Canonical serialization and signed-envelope verification.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import hashlib | ||
| import hmac | ||
| import json | ||
| from collections.abc import Mapping | ||
| from dataclasses import dataclass, field | ||
| from datetime import datetime, timezone | ||
| from typing import Any | ||
|
|
||
| from .registry import validate_contract, validate_json_schema | ||
|
|
||
|
|
||
| class ContractEnvelopeError(ValueError): | ||
| """A signed contract envelope failed a named fail-closed check.""" | ||
|
|
||
| def __init__(self, code: str, detail: str) -> None: | ||
| self.code = code | ||
| self.detail = detail | ||
| super().__init__(f"{code}: {detail}") | ||
|
|
||
|
|
||
| def canonical_json(value: Any) -> str: | ||
| """Serialize JSON data with stable UTF-8 cross-runtime bytes.""" | ||
| _reject_lone_surrogates(value) | ||
| try: | ||
| return json.dumps( | ||
| value, | ||
| allow_nan=False, | ||
| ensure_ascii=False, | ||
| separators=(",", ":"), | ||
| sort_keys=True, | ||
| ) | ||
| except (TypeError, ValueError) as exc: | ||
| raise ContractEnvelopeError("canonicalization_failed", str(exc)) from exc | ||
|
|
||
|
|
||
| def _reject_lone_surrogates(value: Any) -> None: | ||
| if isinstance(value, str): | ||
| if any(0xD800 <= ord(character) <= 0xDFFF for character in value): | ||
| raise ContractEnvelopeError( | ||
| "canonicalization_failed", | ||
| "lone surrogate code points are not valid contract text", | ||
| ) | ||
| return | ||
| if isinstance(value, Mapping): | ||
| for key, item in value.items(): | ||
| _reject_lone_surrogates(key) | ||
| _reject_lone_surrogates(item) | ||
| return | ||
| if isinstance(value, (list, tuple)): | ||
| for item in value: | ||
| _reject_lone_surrogates(item) | ||
|
|
||
|
|
||
| def contract_payload_hash(contract: Mapping[str, Any]) -> str: | ||
| """Hash a contract after excluding its self-referential payloadHash.""" | ||
| unsigned = dict(contract) | ||
| unsigned.pop("payloadHash", None) | ||
| return hashlib.sha256(canonical_json(unsigned).encode("utf-8")).hexdigest() | ||
|
|
||
|
|
||
| def with_payload_hash(contract: Mapping[str, Any]) -> dict[str, Any]: | ||
| """Return a detached contract with its canonical payload hash.""" | ||
| result = json.loads(canonical_json(contract)) | ||
| result["payloadHash"] = contract_payload_hash(result) | ||
| return result | ||
|
|
||
|
|
||
| def _unsigned_envelope(envelope: Mapping[str, Any]) -> dict[str, Any]: | ||
| unsigned = dict(envelope) | ||
| unsigned.pop("signature", None) | ||
| return unsigned | ||
|
|
||
|
|
||
| def _signature(envelope: Mapping[str, Any], secret: str) -> str: | ||
| digest = hmac.new( | ||
| secret.encode("utf-8"), | ||
| canonical_json(_unsigned_envelope(envelope)).encode("utf-8"), | ||
| hashlib.sha256, | ||
| ).hexdigest() | ||
| return f"sha256={digest}" | ||
|
|
||
|
|
||
| def sign_envelope( | ||
| contract: Mapping[str, Any], | ||
| *, | ||
| secret: str, | ||
| key_id: str, | ||
| issued_at: str, | ||
| expires_at: str, | ||
| nonce: str, | ||
| ) -> dict[str, Any]: | ||
| """Build and sign one v1 transport envelope.""" | ||
| if not secret: | ||
| raise ContractEnvelopeError("unknown_key", key_id) | ||
| expected_hash = contract_payload_hash(contract) | ||
| if not hmac.compare_digest(str(contract.get("payloadHash", "")), expected_hash): | ||
| raise ContractEnvelopeError("payload_hash_mismatch", "contract payloadHash is invalid") | ||
| validated = validate_contract(contract) | ||
| issued = _timestamp(issued_at, field_name="issuedAt") | ||
| expires = _timestamp(expires_at, field_name="expiresAt") | ||
| if issued >= expires: | ||
| raise ContractEnvelopeError("lifetime_invalid", "issuedAt must precede expiresAt") | ||
| envelope: dict[str, Any] = { | ||
| "algorithm": "hmac-sha256-v1", | ||
| "contract": validated, | ||
| "envelopeSchemaName": "SignedContractEnvelope", | ||
| "envelopeVersion": "1.0.0", | ||
| "expiresAt": expires_at, | ||
| "issuedAt": issued_at, | ||
| "keyId": key_id, | ||
| "nonce": nonce, | ||
| } | ||
| envelope["signature"] = _signature(envelope, secret) | ||
| validate_json_schema("SignedContractEnvelope", envelope) | ||
|
hudsonaikins marked this conversation as resolved.
|
||
| return envelope | ||
|
|
||
|
|
||
| @dataclass | ||
| class InMemoryNonceReplayGuard: | ||
| """Deterministic injected replay guard for local and test consumers.""" | ||
|
|
||
| _seen: set[tuple[str, str]] = field(default_factory=set) | ||
|
|
||
| def consume(self, key_id: str, nonce: str) -> bool: | ||
| """Return false for a replay; otherwise retain the nonce.""" | ||
| identity = (key_id, nonce) | ||
| if identity in self._seen: | ||
| return False | ||
| self._seen.add(identity) | ||
| return True | ||
|
|
||
|
|
||
| def _timestamp(value: str, *, field_name: str) -> datetime: | ||
| try: | ||
| parsed = datetime.fromisoformat(value.replace("Z", "+00:00")) | ||
| except ValueError as exc: | ||
| raise ContractEnvelopeError("timestamp_invalid", field_name) from exc | ||
| if parsed.tzinfo is None or parsed.utcoffset() is None: | ||
| raise ContractEnvelopeError("timestamp_invalid", field_name) | ||
| return parsed.astimezone(timezone.utc) | ||
|
|
||
|
|
||
| def verify_envelope( | ||
| envelope: Mapping[str, Any], | ||
| *, | ||
| secrets: Mapping[str, str], | ||
| replay_guard: InMemoryNonceReplayGuard, | ||
| now: datetime, | ||
| ) -> dict[str, Any]: | ||
| """Verify schema, hash, lifetime, signature, and nonce exactly once.""" | ||
| try: | ||
| validated_envelope = validate_json_schema("SignedContractEnvelope", dict(envelope)) | ||
| except ValueError as exc: | ||
| raise ContractEnvelopeError("schema_invalid", str(exc)) from exc | ||
|
|
||
| raw_contract = validated_envelope["contract"] | ||
| expected_hash = contract_payload_hash(raw_contract) | ||
| if not hmac.compare_digest(str(raw_contract["payloadHash"]), expected_hash): | ||
| raise ContractEnvelopeError("payload_hash_mismatch", "contract payloadHash is invalid") | ||
| try: | ||
| contract = validate_contract(raw_contract) | ||
| except ValueError as exc: | ||
| raise ContractEnvelopeError("schema_invalid", str(exc)) from exc | ||
|
|
||
| if now.tzinfo is None or now.utcoffset() is None: | ||
| raise ContractEnvelopeError("timestamp_invalid", "now") | ||
| current = now.astimezone(timezone.utc) | ||
| issued_at = _timestamp(str(validated_envelope["issuedAt"]), field_name="issuedAt") | ||
| expires_at = _timestamp(str(validated_envelope["expiresAt"]), field_name="expiresAt") | ||
| if issued_at >= expires_at: | ||
| raise ContractEnvelopeError("lifetime_invalid", "issuedAt must precede expiresAt") | ||
| if current < issued_at: | ||
| raise ContractEnvelopeError("not_yet_valid", "envelope issuedAt is in the future") | ||
| if current >= expires_at: | ||
| raise ContractEnvelopeError("expired", "envelope lifetime ended") | ||
|
|
||
| key_id = str(validated_envelope["keyId"]) | ||
| secret = secrets.get(key_id) | ||
| if not secret: | ||
| raise ContractEnvelopeError("unknown_key", key_id) | ||
|
hudsonaikins marked this conversation as resolved.
|
||
| expected_signature = _signature(validated_envelope, secret) | ||
| if not hmac.compare_digest(str(validated_envelope["signature"]), expected_signature): | ||
| raise ContractEnvelopeError("bad_signature", "signature verification failed") | ||
|
|
||
| nonce = str(validated_envelope["nonce"]) | ||
| if not replay_guard.consume(key_id, nonce): | ||
| raise ContractEnvelopeError("nonce_replay", nonce) | ||
| return contract | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.