feat(openapi): complete upload preview and UI evidence

This commit is contained in:
2026-08-28 15:38:14 +03:00
parent d2849ea3fe
commit 2c94af6791
35 changed files with 3655 additions and 537 deletions
+147
View File
@@ -0,0 +1,147 @@
#!/usr/bin/env python3
"""Validate a sanitized evidence candidate against capability-baseline $defs/run."""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any
MAX_INPUT_BYTES = 65_536
class ValidationError(Exception):
def __init__(self, pointer: str, message: str) -> None:
super().__init__(message)
self.pointer = pointer
self.message = message
def reject_duplicates(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
result: dict[str, Any] = {}
for key, value in pairs:
if key in result:
raise ValidationError('/', 'duplicate JSON key')
result[key] = value
return result
def load_json(path: Path) -> Any:
try:
raw = path.read_bytes()
except OSError as error:
raise ValidationError('/', 'cannot read input') from error
if len(raw) > MAX_INPUT_BYTES:
raise ValidationError('/', 'input exceeds 64 KiB')
try:
return json.loads(raw.decode('utf-8'), object_pairs_hook=reject_duplicates)
except (UnicodeDecodeError, json.JSONDecodeError, RecursionError) as error:
raise ValidationError('/', 'invalid JSON') from error
def pointer_child(pointer: str, key: str | int) -> str:
escaped = str(key).replace('~', '~0').replace('/', '~1')
return f'{pointer}/{escaped}'
def type_matches(value: Any, expected: str) -> bool:
if expected == 'object':
return isinstance(value, dict)
if expected == 'array':
return isinstance(value, list)
if expected == 'string':
return isinstance(value, str)
if expected == 'boolean':
return type(value) is bool
return False
def validate(value: Any, schema: dict[str, Any], pointer: str = '') -> None:
current = pointer or '/'
expected_type = schema.get('type')
if expected_type and not type_matches(value, expected_type):
raise ValidationError(current, f'expected {expected_type}')
if 'const' in schema and value != schema['const']:
raise ValidationError(current, 'does not match const')
if 'enum' in schema and value not in schema['enum']:
raise ValidationError(current, 'does not match enum')
if isinstance(value, str):
if len(value) < schema.get('minLength', 0):
raise ValidationError(current, 'string is too short')
if len(value) > schema.get('maxLength', sys.maxsize):
raise ValidationError(current, 'string is too long')
pattern = schema.get('pattern')
if pattern and not re.fullmatch(pattern, value):
raise ValidationError(current, 'string does not match pattern')
if isinstance(value, list):
if len(value) < schema.get('minItems', 0):
raise ValidationError(current, 'array has too few items')
if len(value) > schema.get('maxItems', sys.maxsize):
raise ValidationError(current, 'array has too many items')
if schema.get('uniqueItems') and len({json.dumps(item, sort_keys=True) for item in value}) != len(value):
raise ValidationError(current, 'array items are not unique')
item_schema = schema.get('items')
if item_schema:
for index, item in enumerate(value):
validate(item, item_schema, pointer_child(pointer, index))
if isinstance(value, dict):
required = schema.get('required', [])
for key in required:
if key not in value:
raise ValidationError(current, f'missing required property {key}')
properties = schema.get('properties', {})
if schema.get('additionalProperties') is False:
unexpected = set(value) - set(properties)
if unexpected:
raise ValidationError(current, f'unexpected property {sorted(unexpected)[0]}')
if len(value) > schema.get('maxProperties', sys.maxsize):
raise ValidationError(current, 'object has too many properties')
for key, item in value.items():
item_schema = properties.get(key)
if item_schema:
validate(item, item_schema, pointer_child(pointer, key))
def run_schema(schema: Any) -> dict[str, Any]:
if not isinstance(schema, dict):
raise ValidationError('/schema', 'schema root must be an object')
definitions = schema.get('$defs')
if not isinstance(definitions, dict):
raise ValidationError('/schema/$defs', 'definitions are missing')
definition = definitions.get('run')
if not isinstance(definition, dict):
raise ValidationError('/schema/$defs/run', 'run definition is missing')
return definition
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--schema', required=True)
parser.add_argument('--candidate', required=True)
parser.add_argument('--require-accepted', action='store_true')
args = parser.parse_args()
try:
schema = load_json(Path(args.schema))
candidate = load_json(Path(args.candidate))
validate(candidate, run_schema(schema))
if args.require_accepted and candidate.get('accepted') is not True:
raise ValidationError('/accepted', 'accepted evidence is required')
except ValidationError as error:
print(f'INVALID_CAPABILITY_RUN pointer={error.pointer} reason={error.message}', file=sys.stderr)
return 1
print('Capability evidence candidate matches $defs/run.')
return 0
if __name__ == '__main__':
raise SystemExit(main())