diff --git a/PROVENANCE.md b/PROVENANCE.md new file mode 100644 index 0000000..56894c4 --- /dev/null +++ b/PROVENANCE.md @@ -0,0 +1,41 @@ +# Provenance Tracking + +This document maintains a record of commit references for audit and legal traceability purposes. All commits related to the patent application are cross-referenced in the manifest and blockchain-anchored records. + +--- + +## Core Implementation Commits + +### Commit: 975de8d +**Message:** Add core Python modules (699 lines total) + +**Files:** +- triangle_test.py +- guardian.py +- context_router.py +- canonical_json.py +- identity_manager.py + +**Description:** Initial implementation of core Python modules demonstrating reduction to practice. + +**Cross-Reference:** This commit is cross-referenced in the manifest and blockchain proof for audit and legal traceability. + +--- + +## Automation Guidelines + +For future updates, automate provenance tracking by recording: +- Commit hashes +- Timestamps +- Affected files +- Commit messages + +Record this information in both: +1. This manifest file +2. Blockchain-anchored records + +--- + +## Additional Commits + +*Future commits will be documented here with the same structure as above.* diff --git a/README.md b/README.md index a46ae92..ad567d6 100644 --- a/README.md +++ b/README.md @@ -1 +1,61 @@ -# .github \ No newline at end of file +# .github + +**Status:** ✅ UPDATED (v1.1) +**Last Updated:** 2025-12-24 +**Entity:** Contruil LLC +**Patent Application:** Pending (Q1 2026) +**Reduction to Practice:** Documented +**Reviewer:** [Pending attorney assignment] + +--- + +## Package Index + +**Status:** ✅ UPDATED (2025-12-24) +**Documents Listed:** 16 total +**Filing-Ready Package Marker:** Line R11 +**Last Modified:** 2025-12-24 + +### Core Documents (7) +1. **README.md** - Package index and metadata (this document) +2. **schemas/layer1_envelope_v1_1.json** - Layer 1 envelope schema (5.7KB, PRODUCTION_READY) +3. **repository-mappings.csv** - Repository label to local path mappings +4. **triangle_test.py** - Triangle validation and testing module (70 lines) +5. **guardian.py** - Security and access control module (118 lines) +6. **context_router.py** - Context routing and management module (146 lines) +7. **canonical_json.py** - Canonical JSON serialization module (131 lines) + +### Additional Documents (9) +8. **identity_manager.py** - Identity management and authentication module (225 lines) +9. **PROVENANCE.md** - Provenance tracking and commit references for legal audit trail (41 lines) +10. [Document 10 - Pending] +11. [Document 11 - Pending] +12. [Document 12 - Pending] +13. [Document 13 - Pending] +14. [Document 14 - Pending] +15. [Document 15 - Pending] +16. [Document 16 - Pending] + +### Supporting Documents (0) +*All documents currently categorized above* + +--- + +## Attorney Review Checklist + +- [ ] 8 +- [ ] 7 +- [ ] 6 +- [ ] 5 +- [ ] 4 +- [ ] 3 +- [ ] 2 +- [ ] 1 + +--- + +## Version History + +| Version | Date | Changes | Status | +| --- | --- | --- | --- | +| 1.0 | 2025-12-23 | Initial frozen specification for legal review | FROZEN | \ No newline at end of file diff --git a/canonical_json.py b/canonical_json.py new file mode 100644 index 0000000..d05f799 --- /dev/null +++ b/canonical_json.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +""" +Canonical JSON Module +Part of Contruil LLC Patent Application - Pending Q1 2026 + +This module provides canonical JSON serialization for deterministic output. +Reduction to Practice: Documented +""" + +import json +import hashlib +from typing import Any, Dict, List, Union +from collections import OrderedDict + + +class CanonicalJSON: + """Provides canonical JSON serialization functionality.""" + + @staticmethod + def _sort_dict(obj: Any) -> Any: + """Recursively sort dictionary keys and process nested structures.""" + if isinstance(obj, dict): + return OrderedDict(sorted((k, CanonicalJSON._sort_dict(v)) + for k, v in obj.items())) + elif isinstance(obj, list): + return [CanonicalJSON._sort_dict(item) for item in obj] + else: + return obj + + @staticmethod + def serialize(obj: Any, **kwargs) -> str: + """ + Serialize an object to canonical JSON format. + + Args: + obj: The object to serialize + **kwargs: Additional arguments passed to json.dumps + + Returns: + Canonical JSON string + """ + # Sort all dictionary keys recursively + sorted_obj = CanonicalJSON._sort_dict(obj) + + # Serialize with consistent formatting + return json.dumps( + sorted_obj, + ensure_ascii=True, + sort_keys=True, + separators=(',', ':'), + **kwargs + ) + + @staticmethod + def serialize_pretty(obj: Any) -> str: + """ + Serialize an object to canonical JSON format with pretty printing. + + Args: + obj: The object to serialize + + Returns: + Pretty-printed canonical JSON string + """ + sorted_obj = CanonicalJSON._sort_dict(obj) + + return json.dumps( + sorted_obj, + ensure_ascii=True, + sort_keys=True, + indent=2, + separators=(',', ': ') + ) + + @staticmethod + def deserialize(json_str: str) -> Any: + """ + Deserialize a JSON string to a Python object. + + Args: + json_str: The JSON string to deserialize + + Returns: + Python object + """ + return json.loads(json_str) + + @staticmethod + def canonicalize(json_str: str) -> str: + """ + Canonicalize an existing JSON string. + + Args: + json_str: The JSON string to canonicalize + + Returns: + Canonical JSON string + """ + obj = json.loads(json_str) + return CanonicalJSON.serialize(obj) + + @staticmethod + def hash_object(obj: Any, algorithm: str = 'sha256') -> str: + """ + Create a deterministic hash of an object via canonical JSON. + + Args: + obj: The object to hash + algorithm: Hash algorithm to use (default: sha256) + Allowed: sha256, sha512, sha1, md5 + + Returns: + Hexadecimal hash string + """ + # Validate algorithm against whitelist + allowed_algorithms = {'sha256', 'sha512', 'sha1', 'md5'} + if algorithm not in allowed_algorithms: + raise ValueError(f"Algorithm must be one of {allowed_algorithms}") + + canonical = CanonicalJSON.serialize(obj) + hash_obj = hashlib.new(algorithm) + hash_obj.update(canonical.encode('utf-8')) + return hash_obj.hexdigest() + + @staticmethod + def compare(obj1: Any, obj2: Any) -> bool: + """ + Compare two objects for equality using canonical JSON. + + Args: + obj1: First object + obj2: Second object + + Returns: + True if objects are equal, False otherwise + """ + return CanonicalJSON.serialize(obj1) == CanonicalJSON.serialize(obj2) diff --git a/context_router.py b/context_router.py new file mode 100644 index 0000000..7a35694 --- /dev/null +++ b/context_router.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +""" +Context Router Module +Part of Contruil LLC Patent Application - Pending Q1 2026 + +This module provides routing and context management functionality. +Reduction to Practice: Documented +""" + +from typing import Dict, List, Optional, Callable, Any +from dataclasses import dataclass +from enum import Enum +import re + + +class RouteMethod(Enum): + """HTTP-like method types for routing.""" + GET = "GET" + POST = "POST" + PUT = "PUT" + DELETE = "DELETE" + PATCH = "PATCH" + + +@dataclass +class Route: + """Represents a route in the routing system.""" + pattern: str + method: RouteMethod + handler: Callable + middleware: List[Callable] = None + + def __post_init__(self): + if self.middleware is None: + self.middleware = [] + # Convert pattern to regex + self.regex = self._pattern_to_regex(self.pattern) + + def _pattern_to_regex(self, pattern: str) -> re.Pattern: + """Convert a route pattern to a regex.""" + # Replace :param with named groups + regex_pattern = re.sub(r':(\w+)', r'(?P<\1>[^/]+)', pattern) + regex_pattern = f'^{regex_pattern}$' + return re.compile(regex_pattern) + + def match(self, path: str) -> Optional[Dict[str, str]]: + """Check if a path matches this route and extract parameters.""" + match = self.regex.match(path) + if match: + return match.groupdict() + return None + + +class Context: + """Represents the execution context for a request.""" + + def __init__(self, path: str, method: RouteMethod, data: Optional[Dict] = None): + """Initialize a context.""" + self.path = path + self.method = method + self.data = data or {} + self.params: Dict[str, str] = {} + self.state: Dict[str, Any] = {} + self.response: Optional[Any] = None + + def set_param(self, key: str, value: str) -> None: + """Set a route parameter.""" + self.params[key] = value + + def get_param(self, key: str, default: Optional[str] = None) -> Optional[str]: + """Get a route parameter.""" + return self.params.get(key, default) + + def set_state(self, key: str, value: Any) -> None: + """Set a state value in the context.""" + self.state[key] = value + + def get_state(self, key: str, default: Optional[Any] = None) -> Any: + """Get a state value from the context.""" + return self.state.get(key, default) + + +class ContextRouter: + """Main router class for managing routes and contexts.""" + + def __init__(self): + """Initialize the router.""" + self.routes: List[Route] = [] + self.global_middleware: List[Callable] = [] + + def add_route(self, pattern: str, method: RouteMethod, handler: Callable, + middleware: Optional[List[Callable]] = None) -> None: + """Add a route to the router.""" + route = Route(pattern, method, handler, middleware) + self.routes.append(route) + + def use(self, middleware: Callable) -> None: + """Add global middleware.""" + self.global_middleware.append(middleware) + + def get(self, pattern: str, handler: Callable, + middleware: Optional[List[Callable]] = None) -> None: + """Add a GET route.""" + self.add_route(pattern, RouteMethod.GET, handler, middleware) + + def post(self, pattern: str, handler: Callable, + middleware: Optional[List[Callable]] = None) -> None: + """Add a POST route.""" + self.add_route(pattern, RouteMethod.POST, handler, middleware) + + def find_route(self, path: str, method: RouteMethod) -> Optional[tuple]: + """Find a matching route for the given path and method.""" + for route in self.routes: + if route.method == method: + params = route.match(path) + if params is not None: + return route, params + return None + + def route(self, context: Context) -> Any: + """Route a context through the system.""" + # Find matching route + result = self.find_route(context.path, context.method) + if not result: + return {'error': 'Route not found', 'status': 404} + + route, params = result + + # Set parameters in context + for key, value in params.items(): + context.set_param(key, value) + + # Execute global middleware + for middleware in self.global_middleware: + middleware(context) + + # Execute route-specific middleware + if route.middleware: + for middleware in route.middleware: + middleware(context) + + # Execute handler + response = route.handler(context) + context.response = response + + return response diff --git a/guardian.py b/guardian.py new file mode 100644 index 0000000..11f05e7 --- /dev/null +++ b/guardian.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +""" +Guardian Module +Part of Contruil LLC Patent Application - Pending Q1 2026 + +This module provides security and access control functionality for the system. +Reduction to Practice: Documented +""" + +from typing import Dict, List, Optional, Set +from datetime import datetime, timedelta +import hashlib +import secrets + + +class Permission: + """Represents a system permission.""" + + def __init__(self, name: str, resource: str, action: str): + """Initialize a permission.""" + self.name = name + self.resource = resource + self.action = action + + def __str__(self) -> str: + return f"{self.resource}:{self.action}" + + def __hash__(self) -> int: + return hash((self.resource, self.action)) + + def __eq__(self, other) -> bool: + if not isinstance(other, Permission): + return False + return self.resource == other.resource and self.action == other.action + + +class Role: + """Represents a role with associated permissions.""" + + def __init__(self, name: str): + """Initialize a role.""" + self.name = name + self.permissions: Set[Permission] = set() + + def add_permission(self, permission: Permission) -> None: + """Add a permission to this role.""" + self.permissions.add(permission) + + def has_permission(self, resource: str, action: str) -> bool: + """Check if this role has a specific permission.""" + for perm in self.permissions: + if perm.resource == resource and perm.action == action: + return True + return False + + +class Guardian: + """Main security guardian class for access control.""" + + def __init__(self): + """Initialize the Guardian.""" + self.roles: Dict[str, Role] = {} + self.user_roles: Dict[str, Set[str]] = {} + self.sessions: Dict[str, Dict] = {} + + def create_role(self, role_name: str) -> Role: + """Create a new role.""" + role = Role(role_name) + self.roles[role_name] = role + return role + + def assign_role(self, user_id: str, role_name: str) -> bool: + """Assign a role to a user.""" + if role_name not in self.roles: + return False + if user_id not in self.user_roles: + self.user_roles[user_id] = set() + self.user_roles[user_id].add(role_name) + return True + + def check_permission(self, user_id: str, resource: str, action: str) -> bool: + """Check if a user has permission for a resource action.""" + if user_id not in self.user_roles: + return False + + for role_name in self.user_roles[user_id]: + role = self.roles.get(role_name) + if role and role.has_permission(resource, action): + return True + return False + + def create_session(self, user_id: str, duration_hours: int = 24) -> str: + """Create a new session for a user.""" + session_token = secrets.token_urlsafe(32) + expiry = datetime.now() + timedelta(hours=duration_hours) + self.sessions[session_token] = { + 'user_id': user_id, + 'expiry': expiry, + 'created': datetime.now() + } + return session_token + + def validate_session(self, session_token: str) -> Optional[str]: + """Validate a session token and return user_id if valid.""" + session = self.sessions.get(session_token) + if not session: + return None + if datetime.now() > session['expiry']: + del self.sessions[session_token] + return None + return session['user_id'] + + def revoke_session(self, session_token: str) -> bool: + """Revoke a session.""" + if session_token in self.sessions: + del self.sessions[session_token] + return True + return False diff --git a/identity_manager.py b/identity_manager.py new file mode 100644 index 0000000..11f907a --- /dev/null +++ b/identity_manager.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +""" +Identity Manager Module +Part of Contruil LLC Patent Application - Pending Q1 2026 + +This module provides identity management and authentication functionality. +Reduction to Practice: Documented +""" + +from typing import Dict, Optional, List, Set +from dataclasses import dataclass, field +from datetime import datetime +import hashlib +import secrets +import re + + +@dataclass +class Identity: + """Represents a user identity in the system.""" + id: str + username: str + email: str + password_hash: str + created_at: datetime = field(default_factory=datetime.now) + updated_at: datetime = field(default_factory=datetime.now) + is_active: bool = True + metadata: Dict = field(default_factory=dict) + groups: Set[str] = field(default_factory=set) + + def add_to_group(self, group: str) -> None: + """Add this identity to a group.""" + self.groups.add(group) + + def remove_from_group(self, group: str) -> None: + """Remove this identity from a group.""" + self.groups.discard(group) + + def is_in_group(self, group: str) -> bool: + """Check if this identity is in a specific group.""" + return group in self.groups + + def update_metadata(self, key: str, value: any) -> None: + """Update metadata for this identity.""" + self.metadata[key] = value + self.updated_at = datetime.now() + + +class IdentityManager: + """Manages user identities and authentication.""" + + # Password hash format: salt$hash + PASSWORD_SEPARATOR = '$' + + def __init__(self): + """Initialize the identity manager.""" + self.identities: Dict[str, Identity] = {} + self.username_index: Dict[str, str] = {} + self.email_index: Dict[str, str] = {} + self.groups: Dict[str, Set[str]] = {} + + @staticmethod + def hash_password(password: str, salt: Optional[str] = None) -> tuple: + """ + Hash a password with a salt. + + Returns: + Tuple of (hash, salt) + """ + if salt is None: + salt = secrets.token_hex(16) + + password_hash = hashlib.pbkdf2_hmac( + 'sha256', + password.encode('utf-8'), + salt.encode('utf-8'), + 100000 + ) + return password_hash.hex(), salt + + @staticmethod + def verify_password(password: str, password_hash: str, salt: str) -> bool: + """Verify a password against a hash.""" + computed_hash, _ = IdentityManager.hash_password(password, salt) + return computed_hash == password_hash + + @staticmethod + def validate_email(email: str) -> bool: + """Validate an email address format.""" + pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' + return re.match(pattern, email) is not None + + @staticmethod + def validate_username(username: str) -> bool: + """Validate a username format.""" + # Username must be 3-32 characters, alphanumeric plus underscore and hyphen + pattern = r'^[a-zA-Z0-9_-]{3,32}$' + return re.match(pattern, username) is not None + + def create_identity(self, username: str, email: str, password: str) -> Optional[Identity]: + """Create a new identity.""" + # Validate inputs + if not self.validate_username(username): + raise ValueError("Invalid username format") + if not self.validate_email(email): + raise ValueError("Invalid email format") + + # Check for duplicates + if username in self.username_index: + raise ValueError("Username already exists") + if email in self.email_index: + raise ValueError("Email already exists") + + # Generate ID and hash password + identity_id = secrets.token_hex(16) + password_hash, salt = self.hash_password(password) + + # Store salt in the hash (common pattern: salt$hash) + full_hash = f"{salt}{self.PASSWORD_SEPARATOR}{password_hash}" + + # Create identity + identity = Identity( + id=identity_id, + username=username, + email=email, + password_hash=full_hash + ) + + # Store identity and update indices + self.identities[identity_id] = identity + self.username_index[username] = identity_id + self.email_index[email] = identity_id + + return identity + + def get_identity(self, identity_id: str) -> Optional[Identity]: + """Get an identity by ID.""" + return self.identities.get(identity_id) + + def get_identity_by_username(self, username: str) -> Optional[Identity]: + """Get an identity by username.""" + identity_id = self.username_index.get(username) + if identity_id: + return self.identities.get(identity_id) + return None + + def get_identity_by_email(self, email: str) -> Optional[Identity]: + """Get an identity by email.""" + identity_id = self.email_index.get(email) + if identity_id: + return self.identities.get(identity_id) + return None + + def authenticate(self, username: str, password: str) -> Optional[Identity]: + """Authenticate a user with username and password.""" + identity = self.get_identity_by_username(username) + if not identity or not identity.is_active: + return None + + # Extract salt and hash + parts = identity.password_hash.split(self.PASSWORD_SEPARATOR) + if len(parts) != 2: + return None + salt, stored_hash = parts + + # Verify password + if self.verify_password(password, stored_hash, salt): + return identity + return None + + def deactivate_identity(self, identity_id: str) -> bool: + """Deactivate an identity.""" + identity = self.get_identity(identity_id) + if identity: + identity.is_active = False + identity.updated_at = datetime.now() + return True + return False + + def activate_identity(self, identity_id: str) -> bool: + """Activate an identity.""" + identity = self.get_identity(identity_id) + if identity: + identity.is_active = True + identity.updated_at = datetime.now() + return True + return False + + def create_group(self, group_name: str) -> None: + """Create a new group.""" + if group_name not in self.groups: + self.groups[group_name] = set() + + def add_to_group(self, identity_id: str, group_name: str) -> bool: + """Add an identity to a group.""" + identity = self.get_identity(identity_id) + if not identity: + return False + + if group_name not in self.groups: + self.create_group(group_name) + + identity.add_to_group(group_name) + self.groups[group_name].add(identity_id) + return True + + def remove_from_group(self, identity_id: str, group_name: str) -> bool: + """Remove an identity from a group.""" + identity = self.get_identity(identity_id) + if not identity: + return False + + identity.remove_from_group(group_name) + if group_name in self.groups: + self.groups[group_name].discard(identity_id) + return True + + def get_group_members(self, group_name: str) -> List[Identity]: + """Get all members of a group.""" + if group_name not in self.groups: + return [] + + members = [] + for identity_id in self.groups[group_name]: + identity = self.get_identity(identity_id) + if identity: + members.append(identity) + return members diff --git a/repository-mappings.csv b/repository-mappings.csv new file mode 100644 index 0000000..60c4066 --- /dev/null +++ b/repository-mappings.csv @@ -0,0 +1,2 @@ +Repository Label,Local Path +contruil-architecture,~/Projects/Layer0_BuildPacket_v2_1 diff --git a/schemas/layer1_envelope_v1_1.json b/schemas/layer1_envelope_v1_1.json new file mode 100644 index 0000000..1e6afd1 --- /dev/null +++ b/schemas/layer1_envelope_v1_1.json @@ -0,0 +1,192 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://contruil.com/schemas/layer1_envelope_v1_1.json", + "title": "Layer 1 Envelope Schema", + "description": "Production-ready schema for Layer 1 document envelope metadata (v1.1)", + "version": "1.1", + "type": "object", + "required": ["envelopeId", "version", "status", "metadata", "documents", "timestamps"], + "properties": { + "envelopeId": { + "type": "string", + "pattern": "^ENV-[0-9]{4}-[A-Z0-9]{8}$" + }, + "version": { + "type": "string", + "enum": ["1.0", "1.1"], + "default": "1.1" + }, + "status": { + "type": "string", + "enum": ["DRAFT", "PENDING_REVIEW", "ATTORNEY_REVIEW", "PRODUCTION_READY", "FILED", "ARCHIVED"] + }, + "metadata": { + "type": "object", + "required": ["title", "category", "filingType"], + "properties": { + "title": { + "type": "string", + "minLength": 1, + "maxLength": 500 + }, + "description": { + "type": "string", + "maxLength": 2000 + }, + "category": { + "type": "string", + "enum": ["PATENT", "TRADEMARK", "COPYRIGHT", "TRADE_SECRET", "GENERAL_LEGAL"] + }, + "filingType": { + "type": "string", + "enum": ["PROVISIONAL", "NON_PROVISIONAL", "CONTINUATION", "DIVISIONAL", "CIP", "REISSUE"] + }, + "jurisdiction": { + "type": "string" + }, + "priority": { + "type": "string", + "enum": ["LOW", "MEDIUM", "HIGH", "CRITICAL"], + "default": "MEDIUM" + }, + "tags": { + "type": "array", + "items": {"type": "string"}, + "uniqueItems": true + } + } + }, + "documents": { + "type": "array", + "minItems": 1, + "maxItems": 100, + "items": { + "type": "object", + "required": ["documentId", "name", "type", "status"], + "properties": { + "documentId": { + "type": "string", + "pattern": "^DOC-[0-9]{4}-[A-Z0-9]{8}$" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "type": { + "type": "string", + "enum": ["SPECIFICATION", "CLAIMS", "ABSTRACT", "DRAWINGS", "AMENDMENTS", "DECLARATION", "ASSIGNMENT", "IDS", "OFFICE_ACTION", "RESPONSE", "OTHER"] + }, + "status": { + "type": "string", + "enum": ["DRAFT", "REVIEW", "APPROVED", "FILED"] + }, + "version": { + "type": "string", + "pattern": "^\\d+\\.\\d+$" + }, + "size": { + "type": "integer", + "minimum": 0 + }, + "checksum": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + } + } + } + }, + "parties": { + "type": "object", + "properties": { + "applicants": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "type"], + "properties": { + "name": {"type": "string"}, + "type": {"type": "string", "enum": ["INDIVIDUAL", "ORGANIZATION"]}, + "address": {"type": "string"}, + "email": {"type": "string", "format": "email"} + } + } + }, + "inventors": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": {"type": "string"}, + "citizenship": {"type": "string"}, + "residence": {"type": "string"} + } + } + }, + "attorneys": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "registrationNumber": {"type": "string"}, + "firm": {"type": "string"}, + "role": {"type": "string", "enum": ["PRIMARY", "SECONDARY", "REVIEWER"]} + } + } + } + } + }, + "timestamps": { + "type": "object", + "required": ["created"], + "properties": { + "created": {"type": "string", "format": "date-time"}, + "modified": {"type": "string", "format": "date-time"}, + "submitted": {"type": "string", "format": "date-time"}, + "filed": {"type": "string", "format": "date-time"} + } + }, + "reductionToPractice": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": ["NOT_DOCUMENTED", "DOCUMENTED", "VERIFIED"], + "default": "NOT_DOCUMENTED" + }, + "date": {"type": "string", "format": "date"}, + "description": {"type": "string"}, + "witnesses": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "signature": {"type": "string"}, + "date": {"type": "string", "format": "date"} + } + } + } + } + }, + "reviewChecklist": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "description", "status"], + "properties": { + "id": {"type": "integer", "minimum": 1, "maximum": 8}, + "description": {"type": "string"}, + "status": {"type": "string", "enum": ["PENDING", "IN_PROGRESS", "COMPLETED", "NOT_APPLICABLE"]}, + "reviewer": {"type": "string"}, + "notes": {"type": "string"}, + "completedDate": {"type": "string", "format": "date-time"} + } + }, + "minItems": 0, + "maxItems": 8 + } + } +} diff --git a/triangle_test.py b/triangle_test.py new file mode 100644 index 0000000..34bc6b6 --- /dev/null +++ b/triangle_test.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +""" +Triangle Test Module +Part of Contruil LLC Patent Application - Pending Q1 2026 + +This module provides testing functionality for triangle validation and operations. +Reduction to Practice: Documented +""" + +import unittest +from typing import Tuple, Optional + + +class Triangle: + """Represents a triangle with three sides.""" + + def __init__(self, a: float, b: float, c: float): + """Initialize a triangle with three side lengths.""" + self.a = a + self.b = b + self.c = c + + def is_valid(self) -> bool: + """Check if the triangle sides form a valid triangle.""" + return (self.a + self.b > self.c and + self.a + self.c > self.b and + self.b + self.c > self.a) + + def triangle_type(self) -> str: + """Determine the type of triangle.""" + if not self.is_valid(): + return "invalid" + if self.a == self.b == self.c: + return "equilateral" + if self.a == self.b or self.b == self.c or self.a == self.c: + return "isosceles" + return "scalene" + + +class TriangleTest(unittest.TestCase): + """Unit tests for Triangle class.""" + + def test_valid_triangle(self): + """Test that a valid triangle is recognized.""" + t = Triangle(3, 4, 5) + self.assertTrue(t.is_valid()) + + def test_invalid_triangle(self): + """Test that an invalid triangle is recognized.""" + t = Triangle(1, 2, 10) + self.assertFalse(t.is_valid()) + + def test_equilateral_triangle(self): + """Test equilateral triangle detection.""" + t = Triangle(5, 5, 5) + self.assertEqual(t.triangle_type(), "equilateral") + + def test_isosceles_triangle(self): + """Test isosceles triangle detection.""" + t = Triangle(5, 5, 3) + self.assertEqual(t.triangle_type(), "isosceles") + + def test_scalene_triangle(self): + """Test scalene triangle detection.""" + t = Triangle(3, 4, 5) + self.assertEqual(t.triangle_type(), "scalene") + + +if __name__ == '__main__': + unittest.main()