diff --git a/.github/workflows/check-canon-compatibility.yml b/.github/workflows/check-canon-compatibility.yml new file mode 100644 index 0000000..6a4d655 --- /dev/null +++ b/.github/workflows/check-canon-compatibility.yml @@ -0,0 +1,83 @@ +name: Check ASET Compatibility Standard + +on: + repository_dispatch: + types: + - aset-standard-released + workflow_dispatch: + inputs: + aset_tag: + description: Exact published ASET Seed release tag to inspect + required: true + type: string + +permissions: + contents: read + +jobs: + check-standard: + runs-on: ubuntu-24.04 + env: + ASET_TAG: >- + ${{ github.event_name == 'repository_dispatch' + && github.event.client_payload.tag + || inputs.aset_tag }} + steps: + - uses: actions/checkout@v4 + + - name: Validate exact release tag + shell: bash + run: | + if [[ "$ASET_TAG" =~ ^seed-[0-9A-Za-z._-]+$ ]]; then + printf 'ASET_TAG=%s\n' "$ASET_TAG" + else + printf 'INVALID_ASET_TAG=%s\n' "$ASET_TAG" >&2 + false + fi + + - name: Download candidate Compatibility Standard identity + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + mkdir -p .aset-candidate + gh release download "$ASET_TAG" \ + --repo attractor-set/ASET \ + --dir .aset-candidate \ + --pattern 'ASET-Seed-*-Compatibility-Standard.json' + + - name: Compare candidate with pinned standard + shell: bash + run: | + python - <<'PY' + import json + from pathlib import Path + + lock = json.loads(Path("canon.lock.json").read_text(encoding="utf-8")) + paths = list(Path(".aset-candidate").glob("ASET-Seed-*-Compatibility-Standard.json")) + if len(paths) != 1: + raise SystemExit(f"candidate standard identity count={len(paths)}") + candidate = json.loads(paths[0].read_text(encoding="utf-8")) + pinned = lock["standard"] + + print("PINNED_STANDARD=" + pinned["standard_id"]) + print("PINNED_PACKAGE_DIGEST=" + lock["required_package_digest"]) + print("CANDIDATE_STANDARD=" + candidate["standard_id"]) + print("CANDIDATE_PACKAGE_DIGEST=" + candidate["canonical_package_digest"]) + + if candidate["standard_id"] == pinned["standard_id"]: + print("STANDARD_COMPATIBILITY=EXACT_PINNED_STANDARD") + print("LOCK_UPDATE_REQUIRED=false") + elif candidate["canonical_package_digest"] == lock["required_package_digest"]: + print("STANDARD_COMPATIBILITY=SAME_CANON_NEW_RELEASE_IDENTITY") + print("LOCK_UPDATE_REQUIRED=explicit_release_rebind") + else: + print("STANDARD_COMPATIBILITY=SEMANTIC_REBASE_REQUIRED") + print("LOCK_UPDATE_REQUIRED=review_required") + PY + + - name: Policy reminder + run: | + printf '%s\n' \ + 'Compatibility Standard lock updates are never applied automatically.' \ + 'A new release identity requires explicit rebind; a changed canonical package requires semantic rebase and full conformance review.' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dcd121e..ae2f82b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,31 +15,121 @@ jobs: steps: - name: Checkout implementation uses: actions/checkout@v4 - - name: Read locked ASET canon reference - id: canon-lock - shell: bash - run: | - CANON_REF="$(python -c 'import json; print(json.load(open("canon.lock.json"))["source"]["ref"])')" - printf 'ref=%s\n' "$CANON_REF" >> "$GITHUB_OUTPUT" - printf 'Locked ASET canon ref: %s\n' "$CANON_REF" - - name: Checkout ASET specification - uses: actions/checkout@v4 - with: - repository: attractor-set/ASET - ref: ${{ steps.canon-lock.outputs.ref }} - path: .aset-spec + - uses: actions/setup-python@v5 with: python-version: "3.12" cache: pip + - run: python -m pip install -r requirements-dev.txt - run: python -m pip install --no-deps --editable . + + - name: Read locked Compatibility Standard release + id: standard-lock + shell: bash + run: | + python - <<'PY' >> "$GITHUB_OUTPUT" + import json + from pathlib import Path + + lock = json.loads(Path("canon.lock.json").read_text(encoding="utf-8")) + standard = lock["standard"] + version = standard["release_version"] + print("tag=" + lock["source"]["tag"]) + print("release_version=" + version) + print("kit_sha256=" + standard["conformance_kit_sha256"].removeprefix("sha256:")) + print("kit=ASET-Seed-" + version + "-Conformance-Kit.zip") + print("checksum=ASET-Seed-" + version + "-Conformance-Kit.zip.sha256") + print("identity=ASET-Seed-" + version + "-Compatibility-Standard.json") + print("manifest=ASET-Seed-" + version + "-Conformance-Kit.manifest.json") + PY + + - name: Download immutable ASET Compatibility Standard assets + env: + GH_TOKEN: ${{ github.token }} + ASET_TAG: ${{ steps.standard-lock.outputs.tag }} + KIT: ${{ steps.standard-lock.outputs.kit }} + CHECKSUM: ${{ steps.standard-lock.outputs.checksum }} + IDENTITY: ${{ steps.standard-lock.outputs.identity }} + MANIFEST: ${{ steps.standard-lock.outputs.manifest }} + shell: bash + run: | + mkdir -p .aset-standard-assets + gh release download "$ASET_TAG" \ + --repo attractor-set/ASET \ + --dir .aset-standard-assets \ + --pattern "$KIT" \ + --pattern "$CHECKSUM" \ + --pattern "$IDENTITY" \ + --pattern "$MANIFEST" + + - name: Verify and materialize exact Conformance Kit + env: + VERSION: ${{ steps.standard-lock.outputs.release_version }} + KIT: ${{ steps.standard-lock.outputs.kit }} + CHECKSUM: ${{ steps.standard-lock.outputs.checksum }} + IDENTITY: ${{ steps.standard-lock.outputs.identity }} + KIT_SHA256: ${{ steps.standard-lock.outputs.kit_sha256 }} + shell: bash + run: | + python - <<'PY' + import hashlib + import json + import os + import shutil + import zipfile + from pathlib import Path, PurePosixPath + + assets = Path(".aset-standard-assets") + kit = assets / os.environ["KIT"] + checksum = assets / os.environ["CHECKSUM"] + identity = assets / os.environ["IDENTITY"] + expected = os.environ["KIT_SHA256"] + actual = hashlib.sha256(kit.read_bytes()).hexdigest() + if actual != expected: + raise SystemExit(f"locked Conformance Kit SHA-256 mismatch: {actual}") + checksum_value = checksum.read_text(encoding="utf-8").split()[0] + if checksum_value != expected: + raise SystemExit("published checksum asset does not match canon.lock.json") + + target = Path(".aset-standard") + if target.exists(): + shutil.rmtree(target) + target.mkdir() + root_name = f"ASET-Seed-{os.environ['VERSION']}-Conformance-Kit" + with zipfile.ZipFile(kit) as archive: + for info in archive.infolist(): + path = PurePosixPath(info.filename) + if path.is_absolute() or ".." in path.parts or not path.parts or path.parts[0] != root_name: + raise SystemExit(f"unsafe Conformance Kit member: {info.filename}") + destination = target.joinpath(*path.parts) + if info.is_dir(): + destination.mkdir(parents=True, exist_ok=True) + else: + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_bytes(archive.read(info)) + embedded = target / root_name / "STANDARD.json" + if embedded.read_bytes() != identity.read_bytes(): + raise SystemExit("published standard identity differs from Conformance Kit STANDARD.json") + print("SEED_CONFORMANCE_KIT_SHA256=" + actual) + print("SEED_CONFORMANCE_KIT_RELEASE_ASSET=PASS") + PY + - name: Verify repository manifest run: python tools/rebuild_manifest.py --check - - name: Verify canon lock - run: python tools/verify_canon_lock.py --canon-root .aset-spec + + - name: Verify exact Compatibility Standard binding + run: >- + python tools/verify_canon_lock.py + --canon-root .aset-standard/ASET-Seed-${{ steps.standard-lock.outputs.release_version }}-Conformance-Kit + --standard-identity .aset-standard-assets/${{ steps.standard-lock.outputs.identity }} + --standard-kit .aset-standard-assets/${{ steps.standard-lock.outputs.kit }} + - name: Run complete profile and release gate - run: python tools/profile_gate.py --canon-root .aset-spec + run: >- + python tools/profile_gate.py + --canon-root .aset-standard/ASET-Seed-${{ steps.standard-lock.outputs.release_version }}-Conformance-Kit + - uses: actions/upload-artifact@v4 if: always() with: diff --git a/.github/workflows/update-canon-lock.yml b/.github/workflows/update-canon-lock.yml deleted file mode 100644 index 6a9d6eb..0000000 --- a/.github/workflows/update-canon-lock.yml +++ /dev/null @@ -1,235 +0,0 @@ -name: Propose ASET canon lock update - -on: - repository_dispatch: - types: - - aset-canon-updated - - workflow_dispatch: - inputs: - aset_ref: - description: Exact ASET commit SHA - required: true - type: string - package_digest: - description: Exact ASET canon package digest - required: true - type: string - -permissions: - contents: write - pull-requests: write - -concurrency: - group: aset-canon-lock-update - cancel-in-progress: false - -jobs: - update-lock: - name: update-canon-lock - runs-on: ubuntu-24.04 - - env: - ASET_REF: >- - ${{ github.event_name == 'repository_dispatch' - && github.event.client_payload.ref - || inputs.aset_ref }} - ASET_PACKAGE_DIGEST: >- - ${{ github.event_name == 'repository_dispatch' - && github.event.client_payload.package_digest - || inputs.package_digest }} - - steps: - - name: Checkout implementation - uses: actions/checkout@v4 - with: - token: ${{ secrets.ASET_PROFILE_AUTOMATION_TOKEN }} - fetch-depth: 0 - - - name: Validate dispatched identities - shell: bash - run: | - set -euo pipefail - - if [[ ! "$ASET_REF" =~ ^[0-9a-f]{40}$ ]]; then - printf 'INVALID_ASET_REF=%s\n' "$ASET_REF" >&2 - false - fi - - if [[ ! "$ASET_PACKAGE_DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]]; then - printf 'INVALID_CANON_DIGEST=%s\n' \ - "$ASET_PACKAGE_DIGEST" >&2 - false - fi - - printf 'ASET_REF=%s\n' "$ASET_REF" - printf 'ASET_PACKAGE_DIGEST=%s\n' "$ASET_PACKAGE_DIGEST" - - - name: Checkout exact ASET revision - uses: actions/checkout@v4 - with: - repository: attractor-set/ASET - ref: ${{ env.ASET_REF }} - path: .aset-spec - - - name: Verify received canon identity - shell: bash - run: | - set -euo pipefail - - ACTUAL_REF="$(git -C .aset-spec rev-parse HEAD)" - - ACTUAL_DIGEST="$( - python - <<'PY' - import json - from pathlib import Path - - package = json.loads( - Path( - ".aset-spec/seed/canonical/CANON_PACKAGE.json" - ).read_text(encoding="utf-8") - ) - - print(package["package_digest"]) - PY - )" - - printf 'ACTUAL_ASET_REF=%s\n' "$ACTUAL_REF" - printf 'ACTUAL_PACKAGE_DIGEST=%s\n' "$ACTUAL_DIGEST" - - test "$ACTUAL_REF" = "$ASET_REF" - test "$ACTUAL_DIGEST" = "$ASET_PACKAGE_DIGEST" - - - name: Update canon lock - id: update - shell: bash - run: | - set -euo pipefail - - python - "$ASET_REF" "$ASET_PACKAGE_DIGEST" <<'PY' - import json - import sys - from pathlib import Path - - path = Path("canon.lock.json") - data = json.loads(path.read_text(encoding="utf-8")) - - data["source"]["ref"] = sys.argv[1] - data["required_package_digest"] = sys.argv[2] - - path.write_text( - json.dumps( - data, - ensure_ascii=False, - indent=2, - sort_keys=True, - ) - + "\n", - encoding="utf-8", - ) - PY - - python tools/rebuild_manifest.py - git diff --check - - if git diff --quiet -- canon.lock.json MANIFEST.json; then - printf 'changed=false\n' >> "$GITHUB_OUTPUT" - printf 'CANON_LOCK_ALREADY_CURRENT=true\n' - else - printf 'changed=true\n' >> "$GITHUB_OUTPUT" - printf 'CANON_LOCK_UPDATE_REQUIRED=true\n' - git diff -- canon.lock.json MANIFEST.json - fi - - - name: Set up Python - if: steps.update.outputs.changed == 'true' - uses: actions/setup-python@v5 - with: - python-version: "3.12" - cache: pip - - - name: Install profile verification dependencies - if: steps.update.outputs.changed == 'true' - shell: bash - run: | - set -euo pipefail - - python -m pip install -r requirements-dev.txt - python -m pip install --no-deps --editable . - - - name: Verify implementation profile - if: steps.update.outputs.changed == 'true' - shell: bash - run: | - set -euo pipefail - - python tools/verify_canon_lock.py \ - --canon-root .aset-spec - - python tools/profile_gate.py \ - --canon-root .aset-spec - - - name: Commit and push update branch - if: steps.update.outputs.changed == 'true' - id: branch - shell: bash - run: | - set -euo pipefail - - SHORT_REF="${ASET_REF:0:12}" - BRANCH="automation/aset-canon-${SHORT_REF}-${GITHUB_RUN_ID}" - - git config user.name "aset-canon-automation" - git config user.email \ - "aset-canon-automation@users.noreply.github.com" - - git switch -c "$BRANCH" - - git add canon.lock.json MANIFEST.json - git diff --cached --check - git diff --cached --stat - - git commit \ - -m "chore(canon): update approved ASET canon lock" \ - -m "ASET commit: ${ASET_REF}" \ - -m "Canon package: ${ASET_PACKAGE_DIGEST}" - - git push origin "$BRANCH" - - printf 'branch=%s\n' "$BRANCH" >> "$GITHUB_OUTPUT" - - - name: Create pull request - if: steps.update.outputs.changed == 'true' - env: - GH_TOKEN: ${{ secrets.ASET_PROFILE_AUTOMATION_TOKEN }} - BRANCH: ${{ steps.branch.outputs.branch }} - shell: bash - run: | - set -euo pipefail - - BODY="$RUNNER_TEMP/canon-lock-pr.md" - - cat > "$BODY" < dict[str, Any]: @@ -18,45 +33,27 @@ def _strict(pairs: list[tuple[str, Any]]) -> dict[str, Any]: def execute_case(case: dict[str, Any]) -> dict[str, Any]: - final_state: dict[str, Any] | None = None - try: - state = core.initialize_state(copy.deepcopy(case["initial_genesis"])) - final_state = state - except core.SeedError as exc: + with tempfile.TemporaryDirectory(prefix="aset-pysql-conformance-") as temp_name: + runtime = DurableSeedRuntime(Path(temp_name) / "case.db") + runtime.initialize(case["initial_store"]) + recognized = case.get("recognized_terminal_record_digests", []) + for setup in case.get("setup", []): + result = runtime.execute( + setup, + recognized_terminal_record_digests=recognized, + ) + if not result["accepted"]: + raise ValueError(f"setup rejected for {case.get('case_id', 'MISSING')}:{result['code']}") + actual = runtime.execute( + case["candidate"], + recognized_terminal_record_digests=recognized, + ) return { "protocol": PROTOCOL, "case_id": case.get("case_id", "MISSING"), - "actual": {"accepted": False, "code": exc.code, "state_changed": False}, - "final_state": None, + "actual": actual, + "final_store": runtime.get_store(), } - for setup in case.get("setup", []): - result = core.apply_transition(state, copy.deepcopy(setup)) - if not result["accepted"] or not result["state_changed"]: - return { - "protocol": PROTOCOL, - "case_id": case.get("case_id", "MISSING"), - "actual": { - "accepted": False, - "code": "SETUP_FAILED:" + result["code"], - "state_changed": False, - }, - "final_state": state, - } - state = result["state"] - final_state = state - result = core.apply_transition(state, copy.deepcopy(case["candidate"])) - if result.get("state") is not None: - final_state = result["state"] - return { - "protocol": PROTOCOL, - "case_id": case.get("case_id", "MISSING"), - "actual": { - "accepted": result["accepted"], - "code": result["code"], - "state_changed": result["state_changed"], - }, - "final_state": final_state, - } def main() -> int: @@ -69,10 +66,18 @@ def main() -> int: response = { "protocol": PROTOCOL, "implementation": { - "profile_id": "ASET-PYTHON-SQLITE-LEARNING-V1", + "profile_id": PROFILE_ID, "normative": False, "language": "Python", "storage": "SQLite", + "seed_canon_id": CANON_ID, + "seed_canon_version": CANON_VERSION, + "seed_package_digest": CANON_PACKAGE_DIGEST, + "seed_release_tag": ASET_RELEASE_TAG, + "seed_release_commit": ASET_RELEASE_COMMIT, + "compatibility_standard_id": STANDARD_ID, + "compatibility_standard_profile_id": STANDARD_PROFILE_ID, + "conformance_kit_sha256": CONFORMANCE_KIT_SHA256, }, "supported_operations": ["describe", "execute_case", "execute_cases"], } @@ -91,7 +96,9 @@ def main() -> int: } else: raise ValueError("OPERATION_UNSUPPORTED") - sys.stdout.write(json.dumps(response, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n") + sys.stdout.write( + json.dumps(response, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n" + ) return 0 except Exception as exc: sys.stderr.write(f"ADAPTER_ERROR={type(exc).__name__}:{exc}\n") diff --git a/src/aset_python_sqlite/cli.py b/src/aset_python_sqlite/cli.py index e1486be..4033814 100644 --- a/src/aset_python_sqlite/cli.py +++ b/src/aset_python_sqlite/cli.py @@ -1,109 +1,94 @@ - from __future__ import annotations import argparse import json -import os -from collections.abc import Sequence from dataclasses import asdict from pathlib import Path +from typing import Any -from .jsonio import StrictJsonError, load_strict -from .proofs import HmacSha256ProofVerifier, RejectAllProofVerifier +from .jsonio import load_strict from .runtime import DurableSeedRuntime +from .seed_binding import binding_document + + +def _object(path: Path) -> dict[str, Any]: + value = load_strict(path) + if not isinstance(value, dict): + raise ValueError(f"{path} must contain a JSON object") + return value -def _load_verifier(path: Path | None): +def _recognized(path: Path | None) -> list[str]: if path is None: - return RejectAllProofVerifier() - if os.name == "posix" and path.stat().st_mode & 0o077: - raise StrictJsonError("proof-secret file must not be group/world accessible") - data = load_strict(path, max_bytes=1024 * 1024) - if not isinstance(data, dict): - raise StrictJsonError("proof-secret document must be an object") - if data.get("document_type") != "aset-seed-hmac-secret-map": - raise StrictJsonError("unsupported proof-secret document type") - if data.get("profile") != "HMAC_SHA256_V1": - raise StrictJsonError("unsupported proof profile") - secrets = data.get("secrets") - if not isinstance(secrets, dict): - raise StrictJsonError("secrets must be an object") - return HmacSha256ProofVerifier.from_base64(secrets) - - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(prog="aset-python-sqlite") - parser.add_argument("--db", type=Path, required=True) - parser.add_argument("--proof-secrets", type=Path) - subparsers = parser.add_subparsers(dest="command", required=True) - - init = subparsers.add_parser("init") - init.add_argument("genesis", type=Path) - - apply = subparsers.add_parser("apply") - apply.add_argument("trust_space_id") - apply.add_argument("transition", type=Path) - - state = subparsers.add_parser("state") - state.add_argument("trust_space_id") - - validate = subparsers.add_parser("validate") - validate.add_argument("trust_space_id") - - backup = subparsers.add_parser("backup") + return [] + value = load_strict(path) + if not isinstance(value, list) or not all(isinstance(item, str) for item in value): + raise ValueError("recognized-terminal file must contain a JSON array of strings") + return value + + +def parser() -> argparse.ArgumentParser: + root = argparse.ArgumentParser(prog="aset-python-sqlite") + root.add_argument("--database", type=Path, default=Path("aset-seed.db")) + sub = root.add_subparsers(dest="command", required=True) + + sub.add_parser("binding") + + initialize = sub.add_parser("init") + initialize.add_argument("--store", type=Path) + + execute = sub.add_parser("execute") + execute.add_argument("--operation", type=Path, required=True) + execute.add_argument("--recognized-terminal", type=Path) + + evaluate = sub.add_parser("evaluate") + evaluate.add_argument("resolution_id") + + sub.add_parser("dump") + sub.add_parser("health") + + backup = sub.add_parser("backup") backup.add_argument("destination", type=Path) + return root - subparsers.add_parser("health") - return parser +def main(argv: list[str] | None = None) -> int: + args = parser().parse_args(argv) + if args.command == "binding": + print(json.dumps(binding_document(), sort_keys=True, indent=2)) + return 0 -def main(argv: Sequence[str] | None = None) -> int: - parser = build_parser() - args = parser.parse_args(argv) try: - verifier = _load_verifier(args.proof_secrets) - runtime = DurableSeedRuntime(args.db, proof_verifier=verifier) + runtime = DurableSeedRuntime(args.database) if args.command == "init": - state = runtime.initialize(load_strict(args.genesis)) - print( - json.dumps( - { - "trust_space_id": state["trust_space_id"], - "state_root": state["current_state_root"], - }, - sort_keys=True, - ) + initial = _object(args.store) if args.store else None + print(json.dumps(runtime.initialize(initial), sort_keys=True)) + elif args.command == "execute": + result = runtime.execute( + _object(args.operation), + recognized_terminal_record_digests=_recognized(args.recognized_terminal), ) - elif args.command == "apply": - result = runtime.apply(args.trust_space_id, load_strict(args.transition)) print(json.dumps(result, sort_keys=True)) - return 0 if result["accepted"] else 2 - elif args.command == "state": - print( - json.dumps( - runtime.get_state(args.trust_space_id), - ensure_ascii=False, - sort_keys=True, - indent=2, + elif args.command == "evaluate": + print(json.dumps(runtime.evaluate(args.resolution_id), sort_keys=True)) + elif args.command == "dump": + print(json.dumps(runtime.get_store(), sort_keys=True)) + elif args.command == "health": + status = asdict(runtime.health()) + print(json.dumps(status, sort_keys=True)) + return 0 if all( + ( + status["database_integrity"] == "ok", + status["seed_binding"] == "PASS", + status["store_validation"] == "PASS", + status["audit_chain"] == "PASS", ) - ) - elif args.command == "validate": - runtime.validate(args.trust_space_id) - print("STATE_VALIDATION=PASS") + ) else 1 elif args.command == "backup": runtime.backup(args.destination) print(f"BACKUP={args.destination}") - elif args.command == "health": - status = asdict(runtime.health()) - print(json.dumps(status, sort_keys=True)) - healthy = ( - status["database_integrity"] == "ok" - and status["state_validation"] == "PASS" - and status["audit_chain"] == "PASS" - ) - return 0 if healthy else 1 except Exception as error: - print(f"ASET_SEED_ERROR={type(error).__name__}:{error}") + print(f"ASET_PYTHON_SQLITE_ERROR={type(error).__name__}:{error}") return 1 return 0 diff --git a/src/aset_python_sqlite/core.py b/src/aset_python_sqlite/core.py deleted file mode 100644 index e7359a7..0000000 --- a/src/aset_python_sqlite/core.py +++ /dev/null @@ -1,2009 +0,0 @@ -from __future__ import annotations - -import copy -import hashlib -import json -import unicodedata -from pathlib import Path -from collections import defaultdict, deque -from typing import Any, Iterable - -from jsonschema import Draft202012Validator -from referencing import Registry, Resource - -VERSION = "0.1-rc11" -SEED_SEMANTICS_ID = "aset-seed:0.1-rc11" -IMPLEMENTATION_VERSION = "0.1-rc12" -DIGEST_PREFIX = "sha256:" - -READINESS_KINDS = {"READINESS_EXECUTE", "READINESS_ACCEPT_RESPONSIBILITY"} -DECISION_CAPABILITY = { - "ISSUE_PERMIT": "ISSUE_PERMIT", - "CONFIRM_OUTCOME": "CONFIRM_OUTCOME", - "SUSPEND_GUARANTEE": "SUSPEND_GUARANTEE", - "TERMINATE_CONTEXT": "TERMINATE_CONTEXT", - "TRANSFER_AUTHORITY": "TRANSFER_AUTHORITY", -} -TERMINAL_PERMIT_STATES = { - "SATISFIED", "EXHAUSTED", "EXPIRED", "REVOKED", "TERMINATED_WITH_CONTEXT", "UNRESOLVED", - "ATTENUATED", -} -NEGATIVE_COMPLETION_STATES = {"EXHAUSTED", "EXPIRED"} - - -class SeedError(Exception): - def __init__(self, code: str): - super().__init__(code) - self.code = code - - -_SCHEMA_VALIDATORS: dict[str, Draft202012Validator] | None = None - - -def _schema_validators() -> dict[str, Draft202012Validator]: - global _SCHEMA_VALIDATORS - if _SCHEMA_VALIDATORS is not None: - return _SCHEMA_VALIDATORS - schema_dir = Path(__file__).resolve().parent / "schemas" - schemas: dict[str, dict[str, Any]] = {} - resources: list[tuple[str, Resource[Any]]] = [] - for path in sorted(schema_dir.glob("*.json")): - schema = json.loads(path.read_text(encoding="utf-8")) - schemas[path.name] = schema - resources.append((schema["$id"], Resource.from_contents(schema))) - registry = Registry().with_resources(resources) - _SCHEMA_VALIDATORS = { - name: Draft202012Validator(schema, registry=registry) - for name, schema in schemas.items() - } - return _SCHEMA_VALIDATORS - - -def _validate_schema(name: str, instance: Any, code: str) -> None: - validator = _schema_validators()[name] - errors = sorted(validator.iter_errors(instance), key=lambda e: (list(e.absolute_path), e.message)) - if errors: - raise SeedError(code) - - -def validate_transition(transition: dict[str, Any]) -> None: - """Validate a transition against the strict public wire schema.""" - _validate_schema("transition.schema.json", transition, "TRANSITION_SCHEMA_INVALID") - - -def _normalize(value: Any) -> Any: - if isinstance(value, str): - return unicodedata.normalize("NFC", value) - if isinstance(value, bool) or value is None or isinstance(value, int): - return value - if isinstance(value, float): - raise SeedError("FLOAT_FORBIDDEN") - if isinstance(value, list): - return [_normalize(v) for v in value] - if isinstance(value, dict): - out: dict[str, Any] = {} - for key, item in value.items(): - if not isinstance(key, str): - raise SeedError("NON_STRING_KEY") - nkey = unicodedata.normalize("NFC", key) - if nkey in out: - raise SeedError("NORMALIZED_KEY_COLLISION") - out[nkey] = _normalize(item) - return out - raise SeedError("UNSUPPORTED_CANONICAL_TYPE") - - -def canonical_bytes(value: Any) -> bytes: - normalized = _normalize(value) - return json.dumps( - normalized, - ensure_ascii=False, - sort_keys=True, - separators=(",", ":"), - allow_nan=False, - ).encode("utf-8") - - -def domain_digest(domain: str, value: Any) -> str: - payload = canonical_bytes(value) - framed = domain.encode("ascii") + b"\x00" + len(payload).to_bytes(8, "big") + payload - return DIGEST_PREFIX + hashlib.sha256(framed).hexdigest() - - -def _hex_id(prefix: str, digest: str) -> str: - if not digest.startswith(DIGEST_PREFIX): - raise SeedError("DIGEST_FORMAT") - return prefix + digest[len(DIGEST_PREFIX):] - - -def artifact_id(kind: str, transition_id: str) -> str: - return _hex_id(kind + ":", domain_digest("ASET/ArtifactID/v1", {"kind": kind, "transition_id": transition_id})) - - -def scope_digest(scope: Iterable[str]) -> str: - return domain_digest("ASET/Scope/v1", sorted(set(scope))) - - -def permit_terms_digest( - delegate_principal_id: str, - task_digest: str, - scope: Iterable[str], - success_predicate_digest: str, - max_attempts: int, - validity_end_ordinal: int, - caveats: dict[str, Any], -) -> str: - return domain_digest("ASET/PermitTerms/v1", { - "delegate_principal_id": delegate_principal_id, - "task_digest": task_digest, - "scope": sorted(set(scope)), - "success_predicate_digest": success_predicate_digest, - "max_attempts": max_attempts, - "stop_on_positive": True, - "validity_end_ordinal": validity_end_ordinal, - "caveats": copy.deepcopy(caveats), - }) - - -def constitution_digest(constitution: dict[str, Any]) -> str: - return domain_digest("ASET/Constitution/v1", constitution) - - -def root_genesis_material(genesis: dict[str, Any]) -> dict[str, Any]: - return { - "schema_version": genesis["schema_version"], - "seed_semantics_id": genesis["seed_semantics_id"], - "constitution_digest": constitution_digest(genesis["constitution"]), - "external_anchor_digest": genesis["external_anchor_digest"], - "root_context_nonce": genesis["root_context_nonce"], - "bootstrap_policy": genesis["bootstrap_policy"], - } - - -def compute_root_genesis_digest(genesis: dict[str, Any]) -> str: - return domain_digest("ASET/RootGenesis/v1", root_genesis_material(genesis)) - - -def compute_context_id(parent_context_id: str | None, context_genesis_digest: str) -> str: - return _hex_id("ctx:", domain_digest( - "ASET/ContextID/v1", - {"parent_context_id": parent_context_id, "context_genesis_digest": context_genesis_digest}, - )) - - -def compute_trust_space_id(seed_semantics_id: str, root_genesis_digest: str, external_anchor_digest: str) -> str: - return _hex_id("ts:", domain_digest( - "ASET/TrustSpaceID/v1", - { - "seed_semantics_id": seed_semantics_id, - "root_genesis_digest": root_genesis_digest, - "external_anchor_digest": external_anchor_digest, - }, - )) - - -def member_genesis_digest(payload: dict[str, Any]) -> str: - material = copy.deepcopy(payload) - material.pop("expected_new_context_id", None) - return domain_digest("ASET/MemberContextGenesis/v1", material) - - -def context_redefinition_proposal_digest(proposal: dict[str, Any]) -> str: - return domain_digest("ASET/ContextRedefinitionProposal/v1", proposal) - - -def transition_digest(transition: dict[str, Any]) -> str: - material = copy.deepcopy(transition) - material.pop("transition_id", None) - return domain_digest("ASET/Transition/v1", material) - - -def compute_transition_id(transition: dict[str, Any]) -> str: - return _hex_id("tx:", transition_digest(transition)) - - -def compute_state_root(state: dict[str, Any]) -> str: - material = copy.deepcopy(state) - material.pop("current_state_root", None) - return domain_digest("ASET/TrustSpaceState/v1", material) - - -def _authority_key(binding: dict[str, Any]) -> tuple[str, str, str]: - # Authority epoch is provenance, not part of the exclusivity key. - return ( - binding["context_id"], - binding["capability_kind"], - binding["scope_digest"], - ) - - -def _authority_id(binding: dict[str, Any]) -> str: - return _hex_id("auth:", domain_digest("ASET/AuthorityBinding/v1", { - "context_id": binding["context_id"], - "capability_kind": binding["capability_kind"], - "scope_digest": binding["scope_digest"], - "holder_principal_id": binding["holder_principal_id"], - "authority_epoch": binding["authority_epoch"], - "grant_provenance": binding["grant_provenance"], - })) - - -def _binding_from_spec(context_id: str, spec: dict[str, Any], provenance: str, epoch: int = 0) -> dict[str, Any]: - scope = sorted(set(spec["scope"])) - binding = { - "authority_id": "", - "context_id": context_id, - "capability_kind": spec["capability_kind"], - "scope": scope, - "scope_digest": scope_digest(scope), - "holder_principal_id": spec["holder_principal_id"], - "authority_epoch": epoch, - "status": "ACTIVE", - "grant_provenance": provenance, - } - binding["authority_id"] = _authority_id(binding) - return binding - - -def _active_authorities(state: dict[str, Any], context_id: str, capability: str, holder: str) -> list[dict[str, Any]]: - return [ - a for a in state["authorities"].values() - if a["status"] == "ACTIVE" - and a["context_id"] == context_id - and a["capability_kind"] == capability - and a["holder_principal_id"] == holder - ] - - -def has_authority( - state: dict[str, Any], context_id: str, capability: str, holder: str, required_scope: Iterable[str] | None = None -) -> bool: - required = set(required_scope or []) - matches = _active_authorities(state, context_id, capability, holder) - for binding in matches: - scope = set(binding["scope"]) - if "*" in scope or required.issubset(scope): - return True - return False - - -def _require_authority( - state: dict[str, Any], transition: dict[str, Any], capability: str, required_scope: Iterable[str] | None = None -) -> None: - actor = transition["authn"]["signer_principal_id"] - if not has_authority(state, transition["context_id"], capability, actor, required_scope): - raise SeedError("AUTHORITY_MISSING") - - -def _context_descendants( - state: dict[str, Any], context_id: str, *, active_only: bool = False -) -> set[str]: - children: dict[str, list[str]] = defaultdict(list) - for cid, context in state["contexts"].items(): - parent = context["parent_context_id"] - if parent is None: - continue - if active_only and context["lifecycle"] != "ACTIVE": - continue - children[parent].append(cid) - result: set[str] = set() - queue = deque(children.get(context_id, [])) - while queue: - current = queue.popleft() - if current in result: - continue - result.add(current) - queue.extend(children.get(current, [])) - return result - - -def _direct_child_owner(state: dict[str, Any], parent_context_id: str, context_id: str) -> str | None: - current = context_id - while current in state["contexts"]: - parent = state["contexts"][current]["parent_context_id"] - if parent == parent_context_id: - return current - if parent is None: - return None - current = parent - return None - - -def compute_affected_sibling_set( - state: dict[str, Any], parent_context_id: str, target_context_id: str -) -> list[str]: - parent = state["contexts"].get(parent_context_id) - target = state["contexts"].get(target_context_id) - if parent is None or target is None: - raise SeedError("CONTEXT_UNKNOWN") - if parent["lifecycle"] != "ACTIVE" or target["lifecycle"] != "ACTIVE": - raise SeedError("AFFECTED_CONTEXT_INACTIVE") - if target["parent_context_id"] != parent_context_id: - raise SeedError("TARGET_NOT_DIRECT_CHILD") - direct_live = { - cid for cid, context in state["contexts"].items() - if context["parent_context_id"] == parent_context_id and context["lifecycle"] == "ACTIVE" - } - reverse: dict[str, set[str]] = defaultdict(set) - for edge in state["normative_dependencies"]: - if edge["dependency_kind"] != "NORMATIVE": - continue - source = edge["source_context_id"] - target_ref = edge["target_context_id"] - if state["contexts"].get(source, {}).get("lifecycle") != "ACTIVE": - continue - if state["contexts"].get(target_ref, {}).get("lifecycle") != "ACTIVE": - continue - source_owner = _direct_child_owner(state, parent_context_id, source) - target_owner = _direct_child_owner(state, parent_context_id, target_ref) - if source_owner in direct_live and target_owner in direct_live and source_owner != target_owner: - reverse[target_owner].add(source_owner) - affected = {target_context_id} - queue = deque([target_context_id]) - while queue: - current = queue.popleft() - for source in sorted(reverse.get(current, set())): - if source not in affected: - affected.add(source) - queue.append(source) - return sorted(affected) - -def _path_is_immune(path: str, immunities: list[str]) -> bool: - for pattern in immunities: - if pattern.endswith("/*") and path.startswith(pattern[:-1]): - return True - if path == pattern: - return True - return False - - -def _validate_context_tree(state: dict[str, Any]) -> None: - contexts = state["contexts"] - root = state["root_context_id"] - if root not in contexts: - raise SeedError("ROOT_CONTEXT_MISSING") - if contexts[root]["parent_context_id"] is not None: - raise SeedError("ROOT_PARENT_FORBIDDEN") - roots = [cid for cid, c in contexts.items() if c["parent_context_id"] is None] - if roots != [root]: - raise SeedError("MULTIPLE_ROOT_CONTEXTS") - for cid, context in contexts.items(): - if context["context_id"] != cid: - raise SeedError("CONTEXT_MAP_KEY_MISMATCH") - if compute_context_id(context["parent_context_id"], context["genesis_digest"]) != cid: - raise SeedError("CONTEXT_ID_MISMATCH") - parent = context["parent_context_id"] - if parent is not None and parent not in contexts: - raise SeedError("CONTEXT_PARENT_MISSING") - for cid in contexts: - seen: set[str] = set() - current: str | None = cid - while current is not None: - if current in seen: - raise SeedError("CONTEXT_CYCLE") - seen.add(current) - current = contexts[current]["parent_context_id"] - - - -def _validate_normative_dependencies(state: dict[str, Any]) -> None: - seen: set[tuple[str, str, str]] = set() - for edge in state["normative_dependencies"]: - source = edge["source_context_id"] - target = edge["target_context_id"] - kind = edge["dependency_kind"] - key = (source, target, kind) - if key in seen: - raise SeedError("DEPENDENCY_DUPLICATE") - seen.add(key) - if source == target: - raise SeedError("DEPENDENCY_SELF_REFERENCE") - if source not in state["contexts"] or target not in state["contexts"]: - raise SeedError("DEPENDENCY_CONTEXT_UNKNOWN") - if kind == "NORMATIVE" and ( - state["contexts"][source]["lifecycle"] != "ACTIVE" - or state["contexts"][target]["lifecycle"] != "ACTIVE" - ): - raise SeedError("NORMATIVE_DEPENDENCY_CONTEXT_INACTIVE") - - -def _scopes_overlap(left: Iterable[str], right: Iterable[str]) -> bool: - a, b = set(left), set(right) - return "*" in a or "*" in b or bool(a & b) - - -def _validate_authority_uniqueness(state: dict[str, Any]) -> None: - active: list[dict[str, Any]] = [] - for aid, binding in state["authorities"].items(): - if binding["authority_id"] != aid: - raise SeedError("AUTHORITY_MAP_KEY_MISMATCH") - if binding["scope_digest"] != scope_digest(binding["scope"]): - raise SeedError("AUTHORITY_SCOPE_DIGEST_MISMATCH") - if _authority_id(binding) != aid: - raise SeedError("AUTHORITY_ID_MISMATCH") - if binding["context_id"] not in state["contexts"]: - raise SeedError("AUTHORITY_CONTEXT_MISSING") - if binding["status"] == "ACTIVE": - for other in active: - if ( - other["context_id"] == binding["context_id"] - and other["capability_kind"] == binding["capability_kind"] - and _scopes_overlap(other["scope"], binding["scope"]) - ): - raise SeedError("AUTHORITY_SCOPE_OVERLAP_ACTIVE") - active.append(binding) - -def _validate_artifact_maps(state: dict[str, Any]) -> None: - map_ids = { - "decisions": "decision_id", - "permits": "permit_id", - "execution_intents": "execution_intent_id", - "permit_use_receipts": "receipt_id", - "observations": "observation_id", - "verifications": "verification_id", - "outcomes": "outcome_id", - "exports": "export_id", - "imports": "import_id", - "reconciliations": "reconciliation_id", - "membership_withdrawals": "withdrawal_id", - "context_redefinitions": "redefinition_id", - "corrections": "correction_id", - } - for map_name, id_name in map_ids.items(): - for key, value in state[map_name].items(): - if value[id_name] != key: - raise SeedError("ARTIFACT_MAP_KEY_MISMATCH") - if value["context_id"] not in state["contexts"]: - raise SeedError("ARTIFACT_CONTEXT_MISSING") - - -def _validate_permit_lineage(state: dict[str, Any]) -> None: - receipts_by_permit: dict[str, list[dict[str, Any]]] = defaultdict(list) - children_by_parent: dict[str, list[dict[str, Any]]] = defaultdict(list) - for receipt in state["permit_use_receipts"].values(): - permit_id = receipt["permit_ref"] - if permit_id not in state["permits"]: - raise SeedError("RECEIPT_PERMIT_MISSING") - receipts_by_permit[permit_id].append(receipt) - intent = state["execution_intents"].get(receipt["execution_intent_ref"]) - if intent is None or intent["permit_ref"] != permit_id: - raise SeedError("RECEIPT_INTENT_MISMATCH") - for permit in state["permits"].values(): - if permit["parent_permit_ref"] is not None: - children_by_parent[permit["parent_permit_ref"]].append(permit) - for permit_id, permit in state["permits"].items(): - if permit["scope_digest"] != scope_digest(permit["scope"]): - raise SeedError("PERMIT_SCOPE_DIGEST_MISMATCH") - if permit["success_predicate_digest"] not in set(state["constitution"]["body"]["rules"].values()): - raise SeedError("PERMIT_SUCCESS_PREDICATE_UNRECOGNIZED") - readiness = state["decisions"].get(permit["readiness_ref"]) - if readiness is None or readiness["decision_kind"] not in READINESS_KINDS: - raise SeedError("PERMIT_READINESS_MISSING") - if readiness["context_id"] != permit["context_id"] or readiness["subject_principal_id"] != permit["delegate_principal_id"]: - raise SeedError("PERMIT_READINESS_BINDING_MISMATCH") - expected_terms = permit_terms_digest( - permit["delegate_principal_id"], permit["task_digest"], permit["scope"], - permit["success_predicate_digest"], permit["max_attempts"], - permit["validity_end_ordinal"], permit["caveats"], - ) - if readiness["conditions_digest"] != expected_terms: - raise SeedError("PERMIT_READINESS_TERMS_MISMATCH") - if permit["parent_permit_ref"] is None: - decision = state["decisions"].get(permit["decision_ref"]) - if decision is None or decision["decision_kind"] != "ISSUE_PERMIT": - raise SeedError("PERMIT_DECISION_MISSING") - if decision["context_id"] != permit["context_id"] or decision["subject_principal_id"] != permit["delegate_principal_id"]: - raise SeedError("PERMIT_DECISION_BINDING_MISMATCH") - if decision["conditions_digest"] != expected_terms or decision["related_ref"] != permit["readiness_ref"]: - raise SeedError("PERMIT_DECISION_TERMS_MISMATCH") - else: - parent = state["permits"].get(permit["parent_permit_ref"]) - if parent is None or parent["context_id"] != permit["context_id"]: - raise SeedError("PARENT_PERMIT_LINEAGE_MISSING") - if not set(permit["scope"]).issubset(set(parent["scope"])): - raise SeedError("PERMIT_SCOPE_ESCALATION") - if permit["max_attempts"] > parent["max_attempts"] - parent["attempts_used"]: - raise SeedError("PERMIT_ATTEMPT_ESCALATION") - if permit["validity_end_ordinal"] > parent["validity_end_ordinal"]: - raise SeedError("PERMIT_VALIDITY_ESCALATION") - for key, value in parent["caveats"].items(): - if permit["caveats"].get(key) != value: - raise SeedError("PERMIT_CAVEAT_WEAKENED") - receipts = sorted(receipts_by_permit.get(permit_id, []), key=lambda r: r["attempt_index"]) - expected_indices = list(range(1, len(receipts) + 1)) - if [r["attempt_index"] for r in receipts] != expected_indices: - raise SeedError("ATTEMPT_INDEX_GAP") - if permit["attempts_used"] != len(receipts): - raise SeedError("ATTEMPT_COUNTER_MISMATCH") - if permit["attempts_used"] > permit["max_attempts"]: - raise SeedError("ATTEMPT_LIMIT_EXCEEDED") - if permit["final_outcome_ref"] is not None: - outcome = state["outcomes"].get(permit["final_outcome_ref"]) - if outcome is None or outcome["permit_ref"] != permit_id: - raise SeedError("PERMIT_FINAL_OUTCOME_MISMATCH") - for parent_id, children in children_by_parent.items(): - if len(children) != 1: - raise SeedError("PERMIT_ATTENUATION_NOT_LINEAR") - parent = state["permits"].get(parent_id) - if parent is None or parent["status"] != "ATTENUATED": - raise SeedError("PARENT_PERMIT_NOT_ATTENUATED") - for permit_id, permit in state["permits"].items(): - if permit["status"] == "ATTENUATED" and permit_id not in children_by_parent: - raise SeedError("ATTENUATED_PERMIT_CHILD_MISSING") - for submission_id, index in state["submission_index"].items(): - receipt = state["permit_use_receipts"].get(index["receipt_ref"]) - if receipt is None or receipt["submission_id"] != submission_id: - raise SeedError("SUBMISSION_INDEX_MISMATCH") - -def _validate_evidence_lineage(state: dict[str, Any]) -> None: - for observation in state["observations"].values(): - receipt = state["permit_use_receipts"].get(observation["receipt_ref"]) - permit = state["permits"].get(observation["permit_ref"]) - if receipt is None or permit is None or receipt["permit_ref"] != observation["permit_ref"]: - raise SeedError("OBSERVATION_RECEIPT_MISMATCH") - if permit["context_id"] != observation["context_id"]: - raise SeedError("OBSERVATION_CONTEXT_MISMATCH") - recognized_policies = set(state["constitution"]["body"]["rules"].values()) - for verification in state["verifications"].values(): - observation = state["observations"].get(verification["observation_ref"]) - receipt = state["permit_use_receipts"].get(verification["receipt_ref"]) - if observation is None or receipt is None: - raise SeedError("VERIFICATION_LINEAGE_MISSING") - if observation["permit_ref"] != verification["permit_ref"] or receipt["permit_ref"] != verification["permit_ref"]: - raise SeedError("VERIFICATION_PERMIT_MISMATCH") - if observation["context_id"] != verification["context_id"]: - raise SeedError("VERIFICATION_CONTEXT_MISMATCH") - if verification["policy_digest"] not in recognized_policies: - raise SeedError("VERIFICATION_POLICY_UNRECOGNIZED") - permit = state["permits"].get(verification["permit_ref"]) - if permit is None or verification["policy_digest"] != permit["success_predicate_digest"]: - raise SeedError("VERIFICATION_POLICY_PERMIT_MISMATCH") - for outcome in state["outcomes"].values(): - permit = state["permits"].get(outcome["permit_ref"]) - if permit is None or permit["context_id"] != outcome["context_id"]: - raise SeedError("OUTCOME_PERMIT_MISMATCH") - effective_refs = sorted( - verification["verification_id"] - for verification in _effective_verifications_for_permit(state, outcome["permit_ref"]) - if verification["status"] == "PASS" - ) - if sorted(outcome["verification_refs"]) != effective_refs: - raise SeedError("OUTCOME_VERIFICATION_SET_INCOMPLETE") - for verification_ref in outcome["verification_refs"]: - verification = state["verifications"].get(verification_ref) - if verification is None: - raise SeedError("OUTCOME_VERIFICATION_MISSING") - if verification["permit_ref"] != outcome["permit_ref"] or verification["context_id"] != outcome["context_id"]: - raise SeedError("OUTCOME_VERIFICATION_MISMATCH") - for export in state["exports"].values(): - if export["outcome_ref"] is not None: - outcome = state["outcomes"].get(export["outcome_ref"]) - if outcome is None or outcome["context_id"] != export["context_id"]: - raise SeedError("EXPORT_OUTCOME_MISMATCH") - expected_root = domain_digest("ASET/ExportCommit/v1", { - "previous_export_root": export["previous_export_root"], - "claim_digest": export["claim_digest"], - "outcome_ref": export["outcome_ref"], - "transition_ref": export["transition_ref"], - }) - if export["source_export_root"] != expected_root: - raise SeedError("EXPORT_COMMIT_ROOT_MISMATCH") - for record in state["imports"].values(): - export = state["exports"].get(record["export_ref"]) - if export is None or export["claim_digest"] != record["claim_digest"]: - raise SeedError("IMPORT_EXPORT_MISMATCH") - for correction in state["corrections"].values(): - if correction["target_type"] != "VERIFICATION": - raise SeedError("CORRECTION_TARGET_TYPE_UNSUPPORTED") - target_map = state["verifications"] - target = target_map.get(correction["target_ref"]) - if target is None or target["context_id"] != correction["context_id"]: - raise SeedError("CORRECTION_TARGET_MISMATCH") - if correction["replacement_ref"] is not None: - replacement = target_map.get(correction["replacement_ref"]) - if replacement is None or replacement["context_id"] != correction["context_id"]: - raise SeedError("CORRECTION_REPLACEMENT_MISMATCH") - -def _validate_transition_records(state: dict[str, Any]) -> None: - if state["accepted_transition_count"] != len(state["transition_records"]): - raise SeedError("TRANSITION_COUNT_MISMATCH") - artifact_owner: dict[str, str] = {} - for txid, record in state["transition_records"].items(): - if txid != record["transition_id"]: - raise SeedError("TRANSITION_MAP_KEY_MISMATCH") - for artifact in record.get("artifact_refs", []): - if artifact in artifact_owner: - raise SeedError("ARTIFACT_CREATOR_DUPLICATE") - artifact_owner[artifact] = txid - for record in state["transition_records"].values(): - derived_parents: set[str] = set() - for basis_ref in record.get("causal_basis_refs", []): - creator = artifact_owner.get(basis_ref) - if creator is not None: - derived_parents.add(creator) - if sorted(record["causal_parents"]) != sorted(derived_parents): - raise SeedError("CAUSAL_RECORD_BASIS_MISMATCH") - for parent in record["causal_parents"]: - if parent not in state["transition_records"]: - raise SeedError("CAUSAL_PARENT_MISSING") - if state["transition_records"][parent]["accepted_index"] >= record["accepted_index"]: - raise SeedError("CAUSAL_ORDER_INVALID") - for cid, context in state["contexts"].items(): - records = sorted( - (r for r in state["transition_records"].values() if r["context_id"] == cid), - key=lambda r: r["accepted_index"], - ) - if context["local_ordinal"] != len(records): - raise SeedError("LOCAL_ORDINAL_MISMATCH") - internal = ( - domain_digest("ASET/EmptyRootContext/v1", {}) - if context["context_kind"] == "ROOT" - else domain_digest("ASET/EmptyContextState/v1", {"context_id": cid}) - ) - for record in records: - internal = domain_digest("ASET/ContextInternalTransition/v1", { - "previous_internal_state_root": internal, - "transition_id": record["transition_id"], - "transition_digest": record["transition_digest"], - }) - if context["internal_state_root"] != internal: - raise SeedError("CONTEXT_INTERNAL_STATE_ROOT_MISMATCH") - -def _validate_governance_records(state: dict[str, Any]) -> None: - for wid, record in state["membership_withdrawals"].items(): - if record["withdrawal_id"] != wid or record["context_id"] not in state["contexts"]: - raise SeedError("WITHDRAWAL_RECORD_INVALID") - if record["parent_context_id"] != state["contexts"][record["context_id"]]["parent_context_id"]: - raise SeedError("WITHDRAWAL_PARENT_MISMATCH") - if record["transition_ref"] not in state["transition_records"]: - raise SeedError("WITHDRAWAL_TRANSITION_MISSING") - if not record["authorization_proof_digest"].startswith(DIGEST_PREFIX): - raise SeedError("WITHDRAWAL_PROOF_FORMAT") - if record["mode"] == "VOLUNTARY_EXIT": - if record["proposal_digest"] is not None or record["reason_digest"] is None: - raise SeedError("WITHDRAWAL_MODE_BINDING_MISMATCH") - elif record["mode"] == "REDEFINITION": - if record["proposal_digest"] is None or record["reason_digest"] is not None: - raise SeedError("WITHDRAWAL_MODE_BINDING_MISMATCH") - else: - raise SeedError("WITHDRAWAL_MODE_UNSUPPORTED") - for cid in record["withdrawn_context_ids"]: - if cid not in state["contexts"] or state["contexts"][cid]["lifecycle"] == "ACTIVE": - raise SeedError("WITHDRAWAL_LIFECYCLE_MISMATCH") - for rid, record in state["context_redefinitions"].items(): - if record["redefinition_id"] != rid or record["context_id"] not in state["contexts"]: - raise SeedError("REDEFINITION_RECORD_INVALID") - proposal = record["proposal"] - if context_redefinition_proposal_digest(proposal) != record["proposal_digest"]: - raise SeedError("REDEFINITION_PROPOSAL_DIGEST_MISMATCH") - if proposal["parent_context_id"] != record["context_id"] or proposal["target_context_id"] != record["target_context_id"]: - raise SeedError("REDEFINITION_PROPOSAL_RECORD_MISMATCH") - if set(record["affected_context_ids"]) != set(record["successor_map"]): - raise SeedError("REDEFINITION_MAP_INCOMPLETE") - replacement_by_old = {item["old_context_id"]: item for item in proposal["replacements"]} - if len(replacement_by_old) != len(proposal["replacements"]) or set(replacement_by_old) != set(record["affected_context_ids"]): - raise SeedError("REDEFINITION_REPLACEMENT_SET_MISMATCH") - for old, new in record["successor_map"].items(): - if old not in state["contexts"] or new not in state["contexts"]: - raise SeedError("REDEFINITION_CONTEXT_MISSING") - old_context = state["contexts"][old] - new_context = state["contexts"][new] - if old_context["lifecycle"] != "SUPERSEDED": - raise SeedError("REDEFINITION_OLD_NOT_SUPERSEDED") - if new_context["lifecycle"] != "ACTIVE": - raise SeedError("REDEFINITION_SUCCESSOR_NOT_ACTIVE") - if old_context["alias"] != new_context["alias"]: - raise SeedError("REDEFINITION_ALIAS_DISCONTINUITY") - item = replacement_by_old[old] - member_payload = { - "parent_context_id": record["context_id"], - "member_principal_id": old_context["member_principal_id"], - "context_kind": old_context["context_kind"], - "context_genesis_nonce": item["context_genesis_nonce"], - "local_alias": old_context["alias"].rsplit("/", 1)[-1], - "initial_authorities": copy.deepcopy(item["initial_authorities"]), - "depends_on_context_ids": list(item["depends_on_context_ids"]), - } - if new_context["genesis_digest"] != member_genesis_digest(member_payload): - raise SeedError("REDEFINITION_SUCCESSOR_GENESIS_MISMATCH") - if new != compute_context_id(record["context_id"], new_context["genesis_digest"]): - raise SeedError("REDEFINITION_SUCCESSOR_ID_MISMATCH") - expected_targets = {record["successor_map"].get(dep, dep) for dep in item["depends_on_context_ids"]} - actual_targets = { - edge["target_context_id"] for edge in state["normative_dependencies"] - if edge["dependency_kind"] == "NORMATIVE" and edge["source_context_id"] == new - } - if actual_targets != expected_targets: - raise SeedError("REDEFINITION_DEPENDENCY_REMAP_MISMATCH") - if len(record["withdrawal_refs"]) != len(record["affected_context_ids"]): - raise SeedError("REDEFINITION_WITHDRAWAL_SET_MISMATCH") - withdrawals = [] - for ref in record["withdrawal_refs"]: - withdrawal = state["membership_withdrawals"].get(ref) - if withdrawal is None: - raise SeedError("REDEFINITION_WITHDRAWAL_MISSING") - withdrawals.append(withdrawal) - if {item["context_id"] for item in withdrawals} != set(record["affected_context_ids"]): - raise SeedError("REDEFINITION_WITHDRAWAL_SET_MISMATCH") - for withdrawal in withdrawals: - if ( - withdrawal["mode"] != "REDEFINITION" - or withdrawal["proposal_digest"] != record["proposal_digest"] - or withdrawal["transition_ref"] != record["transition_ref"] - ): - raise SeedError("REDEFINITION_WITHDRAWAL_BINDING_MISMATCH") - - -def validate_state(state: dict[str, Any], verify_root: bool = True) -> None: - _validate_schema("trust-space-state.schema.json", state, "STATE_SCHEMA_INVALID") - if state["schema_version"] != VERSION or state["seed_semantics_id"] != SEED_SEMANTICS_ID: - raise SeedError("STATE_VERSION_MISMATCH") - if state["constitution"]["digest"] != constitution_digest(state["constitution"]["body"]): - raise SeedError("CONSTITUTION_DIGEST_MISMATCH") - expected_ts = compute_trust_space_id( - state["seed_semantics_id"], state["root_genesis_digest"], state["external_anchor_digest"] - ) - if state["trust_space_id"] != expected_ts: - raise SeedError("TRUST_SPACE_ID_MISMATCH") - _validate_context_tree(state) - live_contexts = {cid: c for cid, c in state["contexts"].items() if c["lifecycle"] == "ACTIVE"} - aliases = [context["alias"] for context in live_contexts.values()] - if len(aliases) != len(set(aliases)): - raise SeedError("CONTEXT_ALIAS_DUPLICATE") - expected_aliases = {context["alias"]: cid for cid, context in live_contexts.items()} - if state["context_aliases"] != expected_aliases: - raise SeedError("CONTEXT_ALIAS_INDEX_MISMATCH") - if state["constitution"]["epoch"] != 0: - raise SeedError("ROOT_CONSTITUTION_IMMUTABLE") - if state["bootstrap"]["admissions_used"] > state["bootstrap"]["policy"]["max_admissions"]: - raise SeedError("BOOTSTRAP_ADMISSION_LIMIT") - if state["bootstrap"]["open"] == (state["bootstrap"]["admissions_used"] >= state["bootstrap"]["policy"]["max_admissions"]): - raise SeedError("BOOTSTRAP_OPEN_STATE_MISMATCH") - for context in state["contexts"].values(): - if context["lifecycle"] == "ACTIVE" and context["constitution_epoch"] != state["constitution"]["epoch"]: - raise SeedError("CONTEXT_CONSTITUTION_EPOCH_MISMATCH") - _validate_normative_dependencies(state) - _validate_authority_uniqueness(state) - for authority in state["authorities"].values(): - if authority["status"] == "ACTIVE" and state["contexts"][authority["context_id"]]["lifecycle"] != "ACTIVE": - raise SeedError("ACTIVE_AUTHORITY_IN_INACTIVE_CONTEXT") - for permit in state["permits"].values(): - if permit["status"] == "ACTIVE" and state["contexts"][permit["context_id"]]["lifecycle"] != "ACTIVE": - raise SeedError("ACTIVE_PERMIT_IN_INACTIVE_CONTEXT") - _validate_artifact_maps(state) - _validate_permit_lineage(state) - _validate_evidence_lineage(state) - _validate_governance_records(state) - _validate_transition_records(state) - if verify_root and state["current_state_root"] != compute_state_root(state): - raise SeedError("STATE_ROOT_MISMATCH") - -def initialize_state(genesis: dict[str, Any]) -> dict[str, Any]: - _validate_schema("root-genesis.schema.json", genesis, "GENESIS_SCHEMA_INVALID") - if genesis.get("schema_version") != VERSION: - raise SeedError("GENESIS_VERSION_MISMATCH") - if genesis.get("seed_semantics_id") != SEED_SEMANTICS_ID: - raise SeedError("SEED_SEMANTICS_MISMATCH") - c_digest = constitution_digest(genesis["constitution"]) - if genesis.get("expected_constitution_digest") != c_digest: - raise SeedError("CONSTITUTION_DIGEST_MISMATCH") - g_digest = compute_root_genesis_digest(genesis) - if genesis.get("expected_root_genesis_digest") != g_digest: - raise SeedError("ROOT_GENESIS_DIGEST_MISMATCH") - root_context_id = compute_context_id(None, g_digest) - if genesis.get("expected_root_context_id") != root_context_id: - raise SeedError("ROOT_CONTEXT_ID_MISMATCH") - trust_space_id = compute_trust_space_id(SEED_SEMANTICS_ID, g_digest, genesis["external_anchor_digest"]) - if genesis.get("expected_trust_space_id") != trust_space_id: - raise SeedError("TRUST_SPACE_ID_MISMATCH") - root_context = { - "context_id": root_context_id, - "parent_context_id": None, - "context_kind": "ROOT", - "member_principal_id": None, - "genesis_digest": g_digest, - "constitution_epoch": 0, - "local_ordinal": 0, - "lifecycle": "ACTIVE", - "guarantee_status": "CONFIRMED", - "internal_state_root": domain_digest("ASET/EmptyRootContext/v1", {}), - "export_root": domain_digest("ASET/EmptyRootExport/v1", {}), - "last_confirmed_export_root": domain_digest("ASET/EmptyRootExport/v1", {}), - "alias": "/", - } - state: dict[str, Any] = { - "schema_version": VERSION, - "seed_semantics_id": SEED_SEMANTICS_ID, - "trust_space_id": trust_space_id, - "root_genesis_digest": g_digest, - "external_anchor_digest": genesis["external_anchor_digest"], - "root_context_id": root_context_id, - "constitution": {"epoch": 0, "digest": c_digest, "body": copy.deepcopy(genesis["constitution"])}, - "bootstrap": { - "open": True, - "admissions_used": 0, - "policy": copy.deepcopy(genesis["bootstrap_policy"]), - }, - "accepted_transition_count": 0, - "current_state_root": "", - "contexts": {root_context_id: root_context}, - "context_aliases": {"/": root_context_id}, - "authorities": {}, - "decisions": {}, - "permits": {}, - "execution_intents": {}, - "permit_use_receipts": {}, - "submission_index": {}, - "observations": {}, - "verifications": {}, - "outcomes": {}, - "exports": {}, - "imports": {}, - "reconciliations": {}, - "membership_withdrawals": {}, - "context_redefinitions": {}, - "corrections": {}, - "normative_dependencies": [], - "transition_records": {}, - } - state["current_state_root"] = compute_state_root(state) - validate_state(state) - return state - - - -def _artifact_creator_transition(state: dict[str, Any], artifact_ref: str | None) -> str | None: - if artifact_ref is None: - return None - for txid, record in state["transition_records"].items(): - if artifact_ref in record.get("artifact_refs", []): - return txid - authority = state["authorities"].get(artifact_ref) - if authority is not None and authority.get("grant_provenance") in state["transition_records"]: - return authority["grant_provenance"] - return None - - -def _causal_basis_refs(transition: dict[str, Any]) -> list[str]: - p = transition.get("payload", {}) - kind = transition.get("kind") - refs: set[str] = {transition.get("context_id", "")} - fields: dict[str, list[str]] = { - "DECISION": ["related_ref"], - "PERMIT_ISSUE": ["decision_ref", "readiness_ref"], - "PERMIT_ATTENUATE": ["parent_permit_ref", "readiness_ref"], - "PERMIT_USE": ["permit_ref"], - "OBSERVATION": ["permit_ref", "receipt_ref"], - "VERIFICATION": ["permit_ref", "receipt_ref", "observation_ref"], - "OUTCOME": ["permit_ref"], - "EXPORT": ["outcome_ref"], - "IMPORT": ["export_ref", "local_permit_ref", "local_receipt_ref"], - "GUARANTEE_SUSPEND": ["child_context_id"], - "RECONCILE": ["child_context_id"], - "MEMBERSHIP_WITHDRAW": [], - "CONTEXT_REDEFINE": [], - "CONTEXT_TERMINATE": ["child_context_id", "verification_ref"], - "CORRECTION": ["target_ref", "replacement_ref"], - "AUTHORITY_TRANSFER": ["authority_ref", "outcome_ref"], - } - for field in fields.get(kind, []): - value = p.get(field) - if value is not None: - refs.add(value) - if kind == "OUTCOME": - refs.update(p.get("verification_refs", [])) - if kind == "RECONCILE": - refs.update(item.get("commit_id") for item in p.get("lineage", [])) - if kind == "CONTEXT_REDEFINE": - proposal = p.get("proposal", {}) - refs.add(proposal.get("target_context_id")) - refs.update(item.get("old_context_id") for item in proposal.get("replacements", [])) - refs.discard("") - refs.discard(None) - return sorted(refs) - - -def _required_causal_parents(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - refs: set[str] = set() - for artifact_ref in _causal_basis_refs(transition): - txid = _artifact_creator_transition(state, artifact_ref) - if txid: - refs.add(txid) - return sorted(refs) - -def _validate_envelope(state: dict[str, Any], transition: dict[str, Any]) -> str: - validate_state(state) - _validate_schema("transition.schema.json", transition, "TRANSITION_SCHEMA_INVALID") - if transition["trust_space_id"] != state["trust_space_id"]: - raise SeedError("TRUST_SPACE_MISMATCH") - expected_id = compute_transition_id(transition) - if transition["transition_id"] != expected_id: - raise SeedError("TRANSITION_ID_MISMATCH") - digest = transition_digest(transition) - existing = state["transition_records"].get(transition["transition_id"]) - if existing is not None: - if existing["transition_digest"] == digest: - raise SeedError("IDEMPOTENT_REPLAY") - raise SeedError("TRANSITION_ID_COLLISION") - if transition["parent_state_root"] != state["current_state_root"]: - raise SeedError("STALE_PARENT_STATE_ROOT") - if transition["constitution_epoch"] != state["constitution"]["epoch"]: - raise SeedError("CONSTITUTION_EPOCH_MISMATCH") - context = state["contexts"].get(transition["context_id"]) - if context is None: - raise SeedError("CONTEXT_UNKNOWN") - if context["lifecycle"] != "ACTIVE": - raise SeedError("CONTEXT_NOT_ACTIVE") - if context["guarantee_status"] == "SUSPENDED" and transition["kind"] != "PARTITION_LOCAL_TRANSITION": - raise SeedError("SUSPENDED_CONTEXT_COORDINATION_REQUIRED") - if transition["expected_local_ordinal"] != context["local_ordinal"] + 1: - raise SeedError("LOCAL_ORDINAL_MISMATCH") - expected_parents = _required_causal_parents(state, transition) - if transition["causal_parents"] != expected_parents: - raise SeedError("CAUSAL_PARENTS_MISMATCH") - if not transition["authn"]["proof_digest"].startswith(DIGEST_PREFIX): - raise SeedError("AUTHENTICATION_PROOF_FORMAT") - return digest - -def _record_transition( - state: dict[str, Any], transition: dict[str, Any], digest: str, artifacts: list[str] -) -> None: - context = state["contexts"][transition["context_id"]] - context["internal_state_root"] = domain_digest("ASET/ContextInternalTransition/v1", { - "previous_internal_state_root": context["internal_state_root"], - "transition_id": transition["transition_id"], - "transition_digest": digest, - }) - context["local_ordinal"] += 1 - state["accepted_transition_count"] += 1 - state["transition_records"][transition["transition_id"]] = { - "transition_id": transition["transition_id"], - "transition_digest": digest, - "context_id": transition["context_id"], - "kind": transition["kind"], - "causal_parents": list(transition["causal_parents"]), - "causal_basis_refs": _causal_basis_refs(transition), - "artifact_refs": list(artifacts), - "accepted_index": state["accepted_transition_count"], - } - state["current_state_root"] = compute_state_root(state) - -def _handle_member_context_genesis(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - payload = transition["payload"] - parent_id = transition["context_id"] - if payload["parent_context_id"] != parent_id: - raise SeedError("PARENT_CONTEXT_MISMATCH") - signer = transition["authn"]["signer_principal_id"] - if parent_id == state["root_context_id"]: - policy = state["bootstrap"]["policy"] - if not state["bootstrap"]["open"]: - raise SeedError("BOOTSTRAP_CLOSED") - if signer != policy["validator_principal_id"]: - raise SeedError("BOOTSTRAP_VALIDATOR_MISMATCH") - if payload["context_kind"] not in policy["allowed_context_kinds"]: - raise SeedError("BOOTSTRAP_CONTEXT_KIND_FORBIDDEN") - if state["bootstrap"]["admissions_used"] >= policy["max_admissions"]: - raise SeedError("BOOTSTRAP_ADMISSION_LIMIT") - allowed = set(policy["allowed_initial_capabilities"]) - if any(a["capability_kind"] not in allowed for a in payload["initial_authorities"]): - raise SeedError("BOOTSTRAP_CAPABILITY_FORBIDDEN") - else: - _require_authority(state, transition, "CREATE_MEMBER_CONTEXT") - g_digest = member_genesis_digest(payload) - context_id = compute_context_id(parent_id, g_digest) - if context_id in state["contexts"]: - raise SeedError("CONTEXT_ALREADY_EXISTS") - alias_path = state["contexts"][parent_id]["alias"].rstrip("/") + "/" + payload["local_alias"] - if alias_path in state["context_aliases"]: - raise SeedError("CONTEXT_ALIAS_IN_USE") - context = { - "context_id": context_id, - "parent_context_id": parent_id, - "context_kind": payload["context_kind"], - "member_principal_id": payload["member_principal_id"], - "genesis_digest": g_digest, - "constitution_epoch": state["constitution"]["epoch"], - "local_ordinal": 0, - "lifecycle": "ACTIVE", - "guarantee_status": "CONFIRMED", - "internal_state_root": domain_digest("ASET/EmptyContextState/v1", {"context_id": context_id}), - "export_root": domain_digest("ASET/EmptyContextExport/v1", {"context_id": context_id}), - "last_confirmed_export_root": domain_digest("ASET/EmptyContextExport/v1", {"context_id": context_id}), - "alias": alias_path, - } - state["contexts"][context_id] = context - state["context_aliases"][alias_path] = context_id - artifacts = [context_id] - for spec in payload["initial_authorities"]: - binding = _binding_from_spec(context_id, spec, transition["transition_id"], 0) - if binding["authority_id"] in state["authorities"]: - raise SeedError("AUTHORITY_ALREADY_EXISTS") - state["authorities"][binding["authority_id"]] = binding - artifacts.append(binding["authority_id"]) - for target in payload.get("depends_on_context_ids", []): - if target not in state["contexts"]: - raise SeedError("DEPENDENCY_CONTEXT_UNKNOWN") - if state["contexts"][target]["lifecycle"] != "ACTIVE": - raise SeedError("DEPENDENCY_CONTEXT_INACTIVE") - if target == context_id: - raise SeedError("DEPENDENCY_SELF_REFERENCE") - edge = { - "source_context_id": context_id, - "target_context_id": target, - "dependency_kind": "NORMATIVE", - } - if edge not in state["normative_dependencies"]: - state["normative_dependencies"].append(edge) - if parent_id == state["root_context_id"]: - state["bootstrap"]["admissions_used"] += 1 - if state["bootstrap"]["admissions_used"] >= state["bootstrap"]["policy"]["max_admissions"]: - state["bootstrap"]["open"] = False - return artifacts - - -def _handle_decision(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - p = transition["payload"] - signer = transition["authn"]["signer_principal_id"] - if p["decision_kind"] in READINESS_KINDS: - if signer != p["subject_principal_id"]: - raise SeedError("READINESS_SUBJECT_MISMATCH") - else: - capability = DECISION_CAPABILITY.get(p["decision_kind"]) - if capability is None: - raise SeedError("DECISION_KIND_UNSUPPORTED") - _require_authority(state, transition, capability, p["scope"]) - did = artifact_id("dec", transition["transition_id"]) - state["decisions"][did] = { - "decision_id": did, - "context_id": transition["context_id"], - "decision_kind": p["decision_kind"], - "issuer_principal_id": signer, - "subject_principal_id": p["subject_principal_id"], - "scope": sorted(set(p["scope"])), - "scope_digest": scope_digest(p["scope"]), - "conditions_digest": p["conditions_digest"], - "related_ref": p.get("related_ref"), - "constitution_epoch": state["constitution"]["epoch"], - } - return [did] - - -def _handle_permit_issue(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - p = transition["payload"] - _require_authority(state, transition, "ISSUE_PERMIT", p["scope"]) - if p["success_predicate_digest"] not in set(state["constitution"]["body"]["rules"].values()): - raise SeedError("PERMIT_SUCCESS_PREDICATE_UNRECOGNIZED") - issue = state["decisions"].get(p["decision_ref"]) - readiness = state["decisions"].get(p["readiness_ref"]) - signer = transition["authn"]["signer_principal_id"] - if issue is None or issue["decision_kind"] != "ISSUE_PERMIT": - raise SeedError("ISSUE_DECISION_INVALID") - if readiness is None or readiness["decision_kind"] not in READINESS_KINDS: - raise SeedError("READINESS_DECISION_INVALID") - if issue["context_id"] != transition["context_id"] or readiness["context_id"] != transition["context_id"]: - raise SeedError("PERMIT_DECISION_CONTEXT_MISMATCH") - if issue["issuer_principal_id"] != signer: - raise SeedError("ISSUE_DECISION_ISSUER_MISMATCH") - if issue["constitution_epoch"] != state["constitution"]["epoch"] or readiness["constitution_epoch"] != state["constitution"]["epoch"]: - raise SeedError("PERMIT_DECISION_EPOCH_STALE") - if issue["subject_principal_id"] != p["delegate_principal_id"] or readiness["subject_principal_id"] != p["delegate_principal_id"]: - raise SeedError("PERMIT_SUBJECT_MISMATCH") - if readiness["issuer_principal_id"] != p["delegate_principal_id"]: - raise SeedError("READINESS_DELEGATE_MISMATCH") - if set(issue["scope"]) != set(p["scope"]) or set(readiness["scope"]) != set(p["scope"]): - raise SeedError("PERMIT_SCOPE_DECISION_MISMATCH") - if p["max_attempts"] < 1: - raise SeedError("MAX_ATTEMPTS_INVALID") - if p["stop_on_positive"] is not True: - raise SeedError("STOP_ON_POSITIVE_REQUIRED") - if p["validity_end_ordinal"] <= state["contexts"][transition["context_id"]]["local_ordinal"]: - raise SeedError("PERMIT_ALREADY_EXPIRED") - terms = permit_terms_digest( - p["delegate_principal_id"], p["task_digest"], p["scope"], p["success_predicate_digest"], - p["max_attempts"], p["validity_end_ordinal"], p["caveats"], - ) - if issue["conditions_digest"] != terms or readiness["conditions_digest"] != terms: - raise SeedError("PERMIT_TERMS_DECISION_MISMATCH") - if issue["related_ref"] != readiness["decision_id"]: - raise SeedError("ISSUE_DECISION_READINESS_MISMATCH") - pid = artifact_id("permit", transition["transition_id"]) - scope = sorted(set(p["scope"])) - state["permits"][pid] = { - "permit_id": pid, - "context_id": transition["context_id"], - "issuer_principal_id": signer, - "delegate_principal_id": p["delegate_principal_id"], - "decision_ref": p["decision_ref"], - "readiness_ref": p["readiness_ref"], - "task_digest": p["task_digest"], - "scope": scope, - "scope_digest": scope_digest(scope), - "success_predicate_digest": p["success_predicate_digest"], - "max_attempts": p["max_attempts"], - "attempts_used": 0, - "stop_on_positive": p["stop_on_positive"], - "validity_end_ordinal": p["validity_end_ordinal"], - "caveats": copy.deepcopy(p["caveats"]), - "status": "ACTIVE", - "final_outcome_ref": None, - "parent_permit_ref": None, - "constitution_epoch": state["constitution"]["epoch"], - } - return [pid] - - -def _handle_permit_attenuate(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - p = transition["payload"] - parent = state["permits"].get(p["parent_permit_ref"]) - if parent is None: - raise SeedError("PARENT_PERMIT_UNKNOWN") - if parent["context_id"] != transition["context_id"]: - raise SeedError("PARENT_PERMIT_CONTEXT_MISMATCH") - if parent["status"] != "ACTIVE" or parent["final_outcome_ref"] is not None: - raise SeedError("PARENT_PERMIT_NOT_ACTIVE") - signer = transition["authn"]["signer_principal_id"] - if signer not in {parent["delegate_principal_id"], parent["issuer_principal_id"]}: - raise SeedError("PERMIT_ATTENUATOR_UNAUTHORIZED") - child_scope = set(p["scope"]) - if not child_scope.issubset(set(parent["scope"])): - raise SeedError("PERMIT_SCOPE_ESCALATION") - remaining = parent["max_attempts"] - parent["attempts_used"] - if p["max_attempts"] > remaining: - raise SeedError("PERMIT_ATTEMPT_ESCALATION") - if p["validity_end_ordinal"] > parent["validity_end_ordinal"]: - raise SeedError("PERMIT_VALIDITY_ESCALATION") - for key, value in parent["caveats"].items(): - if p["caveats"].get(key) != value: - raise SeedError("PERMIT_CAVEAT_WEAKENED") - readiness = state["decisions"].get(p["readiness_ref"]) - if readiness is None or readiness["decision_kind"] not in READINESS_KINDS: - raise SeedError("READINESS_DECISION_INVALID") - if readiness["context_id"] != transition["context_id"] or readiness["issuer_principal_id"] != p["delegate_principal_id"]: - raise SeedError("READINESS_DELEGATE_MISMATCH") - terms = permit_terms_digest( - p["delegate_principal_id"], parent["task_digest"], p["scope"], parent["success_predicate_digest"], - p["max_attempts"], p["validity_end_ordinal"], p["caveats"], - ) - if readiness["conditions_digest"] != terms or set(readiness["scope"]) != child_scope: - raise SeedError("PERMIT_READINESS_TERMS_MISMATCH") - pid = artifact_id("permit", transition["transition_id"]) - scope = sorted(child_scope) - state["permits"][pid] = { - "permit_id": pid, - "context_id": transition["context_id"], - "issuer_principal_id": signer, - "delegate_principal_id": p["delegate_principal_id"], - "decision_ref": parent["decision_ref"], - "readiness_ref": readiness["decision_id"], - "task_digest": parent["task_digest"], - "scope": scope, - "scope_digest": scope_digest(scope), - "success_predicate_digest": parent["success_predicate_digest"], - "max_attempts": p["max_attempts"], - "attempts_used": 0, - "stop_on_positive": True, - "validity_end_ordinal": p["validity_end_ordinal"], - "caveats": copy.deepcopy(p["caveats"]), - "status": "ACTIVE", - "final_outcome_ref": None, - "parent_permit_ref": parent["permit_id"], - "constitution_epoch": state["constitution"]["epoch"], - } - parent["status"] = "ATTENUATED" - return [pid] - -def _handle_permit_use(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - p = transition["payload"] - permit = state["permits"].get(p["permit_ref"]) - if permit is None: - raise SeedError("PERMIT_UNKNOWN") - if permit["context_id"] != transition["context_id"]: - raise SeedError("PERMIT_CONTEXT_MISMATCH") - if transition["authn"]["signer_principal_id"] != permit["delegate_principal_id"]: - raise SeedError("PERMIT_DELEGATE_MISMATCH") - if permit["status"] != "ACTIVE" or permit["final_outcome_ref"] is not None: - raise SeedError("PERMIT_NOT_ACTIVE") - if state["contexts"][transition["context_id"]]["local_ordinal"] + 1 > permit["validity_end_ordinal"]: - raise SeedError("PERMIT_EXPIRED") - existing = state["submission_index"].get(p["submission_id"]) - if existing is not None: - if existing["permit_ref"] == permit["permit_id"] and existing["candidate_digest"] == p["candidate_digest"]: - raise SeedError("IDEMPOTENT_SUBMISSION_REPLAY") - raise SeedError("SUBMISSION_ID_COLLISION") - if permit["attempts_used"] >= permit["max_attempts"]: - raise SeedError("ATTEMPT_LIMIT_EXHAUSTED") - intent_id = artifact_id("intent", transition["transition_id"]) - receipt_id = artifact_id("receipt", transition["transition_id"]) - attempt_index = permit["attempts_used"] + 1 - state["execution_intents"][intent_id] = { - "execution_intent_id": intent_id, - "context_id": transition["context_id"], - "permit_ref": permit["permit_id"], - "presenter_principal_id": transition["authn"]["signer_principal_id"], - "submission_id": p["submission_id"], - "candidate_digest": p["candidate_digest"], - } - state["permit_use_receipts"][receipt_id] = { - "receipt_id": receipt_id, - "context_id": transition["context_id"], - "permit_ref": permit["permit_id"], - "execution_intent_ref": intent_id, - "presenter_principal_id": transition["authn"]["signer_principal_id"], - "submission_id": p["submission_id"], - "candidate_digest": p["candidate_digest"], - "attempt_index": attempt_index, - } - state["submission_index"][p["submission_id"]] = { - "permit_ref": permit["permit_id"], - "candidate_digest": p["candidate_digest"], - "receipt_ref": receipt_id, - } - permit["attempts_used"] = attempt_index - if attempt_index >= permit["max_attempts"]: - permit["status"] = "EXHAUSTED" - return [intent_id, receipt_id] - - -def _handle_observation(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - p = transition["payload"] - receipt = state["permit_use_receipts"].get(p["receipt_ref"]) - permit = state["permits"].get(p["permit_ref"]) - if receipt is None or permit is None: - raise SeedError("OBSERVATION_LINEAGE_MISSING") - if receipt["permit_ref"] != permit["permit_id"]: - raise SeedError("OBSERVATION_RECEIPT_MISMATCH") - if permit["context_id"] != transition["context_id"]: - raise SeedError("OBSERVATION_CONTEXT_MISMATCH") - if transition["authn"]["signer_principal_id"] != receipt["presenter_principal_id"]: - raise SeedError("OBSERVATION_PRESENTER_MISMATCH") - oid = artifact_id("obs", transition["transition_id"]) - state["observations"][oid] = { - "observation_id": oid, - "context_id": transition["context_id"], - "permit_ref": permit["permit_id"], - "receipt_ref": receipt["receipt_id"], - "observer_principal_id": transition["authn"]["signer_principal_id"], - "claim_digest": p["claim_digest"], - "evidence_refs": list(p["evidence_refs"]), - "claim_subject_context_id": p.get("claim_subject_context_id"), - } - return [oid] - - -def _handle_verification(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - p = transition["payload"] - _require_authority(state, transition, "VERIFY") - observation = state["observations"].get(p["observation_ref"]) - receipt = state["permit_use_receipts"].get(p["receipt_ref"]) - permit = state["permits"].get(p["permit_ref"]) - if observation is None or receipt is None or permit is None: - raise SeedError("VERIFICATION_LINEAGE_MISSING") - if observation["permit_ref"] != permit["permit_id"] or receipt["permit_ref"] != permit["permit_id"]: - raise SeedError("VERIFICATION_PERMIT_MISMATCH") - if observation["receipt_ref"] != receipt["receipt_id"]: - raise SeedError("VERIFICATION_RECEIPT_MISMATCH") - if permit["context_id"] != transition["context_id"] or observation["context_id"] != transition["context_id"]: - raise SeedError("VERIFICATION_CONTEXT_MISMATCH") - allowed = { - "PASS": {"SUCCESS", "FAILURE", "TRUST_LINEAGE_LOST"}, - "FAIL": {"UNDETERMINED"}, - "UNKNOWN": {"UNDETERMINED"}, - } - if p["result_class"] not in allowed[p["status"]]: - raise SeedError("VERIFICATION_STATUS_RESULT_MISMATCH") - recognized_policies = set(state["constitution"]["body"]["rules"].values()) - if p["policy_digest"] not in recognized_policies: - raise SeedError("VERIFICATION_POLICY_UNRECOGNIZED") - if p["policy_digest"] != permit["success_predicate_digest"]: - raise SeedError("VERIFICATION_POLICY_PERMIT_MISMATCH") - vid = artifact_id("ver", transition["transition_id"]) - state["verifications"][vid] = { - "verification_id": vid, - "context_id": transition["context_id"], - "permit_ref": permit["permit_id"], - "receipt_ref": receipt["receipt_id"], - "observation_ref": observation["observation_id"], - "verifier_principal_id": transition["authn"]["signer_principal_id"], - "policy_digest": p["policy_digest"], - "evidence_refs": list(p["evidence_refs"]), - "status": p["status"], - "result_class": p["result_class"], - } - return [vid] - - - -def _effective_verification_ref(state: dict[str, Any], ref: str) -> str | None: - current: str | None = ref - seen: set[str] = set() - while current is not None: - if current in seen: - raise SeedError("CORRECTION_CYCLE") - seen.add(current) - matches = [ - correction for correction in state["corrections"].values() - if correction["target_ref"] == current - ] - if len(matches) > 1: - raise SeedError("CORRECTION_TARGET_MULTIPLE") - if not matches: - return current - current = matches[0]["replacement_ref"] - return None - - -def _effective_verifications_for_permit(state: dict[str, Any], permit_id: str) -> list[dict[str, Any]]: - result: list[dict[str, Any]] = [] - for ref, verification in state["verifications"].items(): - if verification["permit_ref"] != permit_id: - continue - effective = _effective_verification_ref(state, ref) - if effective == ref: - result.append(verification) - return result - - -def _handle_outcome(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - p = transition["payload"] - _require_authority(state, transition, "CONFIRM_OUTCOME") - permit = state["permits"].get(p["permit_ref"]) - if permit is None: - raise SeedError("PERMIT_UNKNOWN") - if permit["context_id"] != transition["context_id"]: - raise SeedError("OUTCOME_CONTEXT_MISMATCH") - if permit["final_outcome_ref"] is not None: - raise SeedError("OUTCOME_ALREADY_FINAL") - if permit["status"] not in {"ACTIVE", "EXHAUSTED", "EXPIRED"}: - raise SeedError("PERMIT_NOT_OUTCOME_ELIGIBLE") - effective = _effective_verifications_for_permit(state, permit["permit_id"]) - effective_pass = [v for v in effective if v["status"] == "PASS"] - effective_refs = sorted(v["verification_id"] for v in effective_pass) - if sorted(p["verification_refs"]) != effective_refs: - raise SeedError("OUTCOME_VERIFICATION_SET_INCOMPLETE") - if not effective_pass: - raise SeedError("OUTCOME_VERIFICATION_MISSING") - has_success = any(v["result_class"] == "SUCCESS" for v in effective_pass) - has_failure = any(v["result_class"] == "FAILURE" for v in effective_pass) - if p["outcome_class"] == "POSITIVE": - if not has_success: - raise SeedError("SUCCESS_NOT_VERIFIED") - else: - if has_success: - raise SeedError("NEGATIVE_CONFLICTS_WITH_SUCCESS") - expired = state["contexts"][transition["context_id"]]["local_ordinal"] + 1 > permit["validity_end_ordinal"] - terminal = permit["status"] in NEGATIVE_COMPLETION_STATES or expired - if not terminal: - raise SeedError("NEGATIVE_NOT_TERMINAL") - if not has_failure: - raise SeedError("FAILURE_NOT_VERIFIED") - if expired and permit["status"] == "ACTIVE": - permit["status"] = "EXPIRED" - oid = artifact_id("out", transition["transition_id"]) - state["outcomes"][oid] = { - "outcome_id": oid, - "context_id": transition["context_id"], - "permit_ref": permit["permit_id"], - "verification_refs": effective_refs, - "confirmer_principal_id": transition["authn"]["signer_principal_id"], - "outcome_class": p["outcome_class"], - } - permit["final_outcome_ref"] = oid - permit["status"] = "SATISFIED" if p["outcome_class"] == "POSITIVE" else "EXHAUSTED" - return [oid] - -def _handle_export(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - _require_authority(state, transition, "EXPORT") - p = transition["payload"] - context = state["contexts"][transition["context_id"]] - if context["guarantee_status"] != "CONFIRMED": - raise SeedError("GUARANTEE_SUSPENDED_USE_LOCAL_COMMIT") - if p["source_export_root"] != context["export_root"]: - raise SeedError("EXPORT_ROOT_MISMATCH") - outcome_ref = p.get("outcome_ref") - if outcome_ref is not None: - outcome = state["outcomes"].get(outcome_ref) - if outcome is None: - raise SeedError("EXPORT_OUTCOME_UNKNOWN") - if outcome["context_id"] != transition["context_id"]: - raise SeedError("EXPORT_OUTCOME_CONTEXT_MISMATCH") - previous_root = context["export_root"] - new_root = domain_digest("ASET/ExportCommit/v1", { - "previous_export_root": previous_root, - "claim_digest": p["claim_digest"], - "outcome_ref": outcome_ref, - "transition_ref": transition["transition_id"], - }) - context["export_root"] = new_root - context["last_confirmed_export_root"] = new_root - eid = artifact_id("export", transition["transition_id"]) - state["exports"][eid] = { - "export_id": eid, - "context_id": transition["context_id"], - "source_context_id": transition["context_id"], - "previous_export_root": previous_root, - "source_export_root": new_root, - "claim_digest": p["claim_digest"], - "outcome_ref": outcome_ref, - "guarantee_status": context["guarantee_status"], - "issuer_principal_id": transition["authn"]["signer_principal_id"], - "transition_ref": transition["transition_id"], - } - return [eid] - -def _handle_import(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - p = transition["payload"] - _require_authority(state, transition, "IMPORT") - export = state["exports"].get(p["export_ref"]) - if export is None: - raise SeedError("EXPORT_UNKNOWN") - permit = state["permits"].get(p["local_permit_ref"]) - receipt = state["permit_use_receipts"].get(p["local_receipt_ref"]) - if permit is None or receipt is None: - raise SeedError("IMPORT_LOCAL_LINEAGE_MISSING") - if receipt["permit_ref"] != permit["permit_id"] or permit["context_id"] != transition["context_id"]: - raise SeedError("IMPORT_LOCAL_LINEAGE_MISMATCH") - signer = transition["authn"]["signer_principal_id"] - if receipt["presenter_principal_id"] != signer or permit["delegate_principal_id"] != signer: - raise SeedError("IMPORT_PRESENTER_MISMATCH") - iid = artifact_id("import", transition["transition_id"]) - oid = artifact_id("obs", transition["transition_id"]) - state["imports"][iid] = { - "import_id": iid, - "context_id": transition["context_id"], - "target_context_id": transition["context_id"], - "export_ref": export["export_id"], - "claim_digest": export["claim_digest"], - "importer_principal_id": transition["authn"]["signer_principal_id"], - } - # Imported claims are Observations, not locally accepted Outcomes. - state["observations"][oid] = { - "observation_id": oid, - "context_id": transition["context_id"], - "permit_ref": p["local_permit_ref"], - "receipt_ref": p["local_receipt_ref"], - "observer_principal_id": transition["authn"]["signer_principal_id"], - "claim_digest": export["claim_digest"], - "evidence_refs": [export["export_id"]], - "claim_subject_context_id": export["source_context_id"], - } - return [iid, oid] - - -def _handle_guarantee_suspend(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - p = transition["payload"] - _require_authority(state, transition, "SUSPEND_GUARANTEE") - child = state["contexts"].get(p["child_context_id"]) - if child is None: - raise SeedError("CONTEXT_UNKNOWN") - if child["parent_context_id"] != transition["context_id"]: - raise SeedError("NOT_DIRECT_CHILD_CONTEXT") - child["guarantee_status"] = "SUSPENDED" - return [] - - -def compute_local_commit_id(parent_export_root: str, operation_class: str, commit_digest: str, signer_principal_id: str) -> str: - return _hex_id("commit:", domain_digest("ASET/LocalCommitID/v1", { - "parent_export_root": parent_export_root, - "operation_class": operation_class, - "commit_digest": commit_digest, - "signer_principal_id": signer_principal_id, - })) - - -def _local_commit_root(parent_export_root: str, operation_class: str, commit_digest: str, commit_id: str) -> str: - return domain_digest("ASET/LocalExportCommit/v1", { - "parent_export_root": parent_export_root, - "operation_class": operation_class, - "commit_digest": commit_digest, - "commit_id": commit_id, - }) - - -def _handle_partition_local_transition(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - p = transition["payload"] - context = state["contexts"][transition["context_id"]] - if context["guarantee_status"] != "SUSPENDED": - raise SeedError("GUARANTEE_NOT_SUSPENDED") - cls = state["constitution"]["body"]["coordination_classes"].get(p["operation_class"], "COORDINATION_REQUIRED") - if cls == "COORDINATION_REQUIRED": - raise SeedError("COORDINATION_REQUIRED") - if cls == "INVARIANT_CONFLUENT": - allowed = set(state["constitution"]["body"].get("accepted_coordination_proofs", [])) - if p.get("coordination_proof_digest") not in allowed: - raise SeedError("COORDINATION_PROOF_MISSING") - signer = transition["authn"]["signer_principal_id"] - if signer != context["member_principal_id"] and not has_authority(state, transition["context_id"], "EXPORT", signer): - raise SeedError("LOCAL_COMMIT_SIGNER_UNAUTHORIZED") - commit_id = compute_local_commit_id(p["parent_export_root"], p["operation_class"], p["commit_digest"], signer) - known_roots = {context["last_confirmed_export_root"]} - known_roots.update(c["new_export_root"] for c in context.get("unconfirmed_commits", {}).values()) - if p["parent_export_root"] not in known_roots: - raise SeedError("LOCAL_COMMIT_PARENT_UNKNOWN") - new_root = _local_commit_root(p["parent_export_root"], p["operation_class"], p["commit_digest"], commit_id) - context.setdefault("unconfirmed_commits", {})[commit_id] = { - "commit_id": commit_id, - "parent_export_root": p["parent_export_root"], - "new_export_root": new_root, - "operation_class": p["operation_class"], - "commit_digest": p["commit_digest"], - "signer_principal_id": transition["authn"]["signer_principal_id"], - } - return [commit_id] - - -def _handle_reconcile(state: dict[str, Any], transition: dict[str, Any]) -> tuple[list[str], str]: - p = transition["payload"] - _require_authority(state, transition, "RECONCILE") - child = state["contexts"].get(p["child_context_id"]) - if child is None: - raise SeedError("CONTEXT_UNKNOWN") - if child["parent_context_id"] != transition["context_id"]: - raise SeedError("NOT_DIRECT_CHILD_CONTEXT") - if child["guarantee_status"] != "SUSPENDED": - raise SeedError("GUARANTEE_NOT_SUSPENDED") - if p["common_export_root"] != child["last_confirmed_export_root"]: - raise SeedError("RECONCILIATION_COMMON_ROOT_MISMATCH") - commits = p["lineage"] - known = child.get("unconfirmed_commits", {}) - submitted_ids = {item["commit_id"] for item in commits} - if known and not set(known).issubset(submitted_ids): - raise SeedError("RECONCILIATION_KNOWN_COMMIT_SET_MISMATCH") - for item in commits: - expected_commit_id = compute_local_commit_id( - item["parent_export_root"], item["operation_class"], item["commit_digest"], item["signer_principal_id"] - ) - if item["commit_id"] != expected_commit_id: - raise SeedError("LOCAL_COMMIT_ID_MISMATCH") - if not item["proof_digest"].startswith(DIGEST_PREFIX): - raise SeedError("AUTHENTICATION_PROOF_FORMAT") - signer = item["signer_principal_id"] - if signer != child["member_principal_id"] and not has_authority(state, child["context_id"], "EXPORT", signer): - raise SeedError("LOCAL_COMMIT_SIGNER_UNAUTHORIZED") - expected_root = _local_commit_root( - item["parent_export_root"], item["operation_class"], item["commit_digest"], item["commit_id"] - ) - if item["new_export_root"] != expected_root: - raise SeedError("LOCAL_COMMIT_ROOT_MISMATCH") - by_parent: dict[str, set[str]] = defaultdict(set) - for item in commits: - by_parent[item["parent_export_root"]].add(item["new_export_root"]) - fork = any(len(children) > 1 for children in by_parent.values()) - current = p["common_export_root"] - accepted_prefix = 0 - invalid_code: str | None = None - seen_ids: set[str] = set() - if not fork: - remaining = {item["commit_id"]: item for item in commits} - while remaining: - candidates = [item for item in remaining.values() if item["parent_export_root"] == current] - if len(candidates) != 1: - invalid_code = "RECONCILIATION_CHAIN_BREAK" - break - item = candidates[0] - if item["commit_id"] in seen_ids: - invalid_code = "RECONCILIATION_DUPLICATE_COMMIT" - break - seen_ids.add(item["commit_id"]) - cls = state["constitution"]["body"]["coordination_classes"].get( - item["operation_class"], "COORDINATION_REQUIRED" - ) - if cls == "COORDINATION_REQUIRED": - invalid_code = "COORDINATION_REQUIRED" - break - current = item["new_export_root"] - accepted_prefix += 1 - del remaining[item["commit_id"]] - rid = artifact_id("reconcile", transition["transition_id"]) - if fork: - result = "FORK_DETECTED" - code = "FORK_DETECTED" - invalid_code = "KNOWN_FORK" - elif invalid_code is not None: - result = "PARTIALLY_CONFIRMED" if accepted_prefix > 0 else "INSUFFICIENT_EVIDENCE" - code = result - if accepted_prefix > 0: - child["last_confirmed_export_root"] = current - child["export_root"] = current - else: - result = "CONFIRMED" - code = "ACCEPTED" - child["last_confirmed_export_root"] = current - child["export_root"] = current - child["guarantee_status"] = "CONFIRMED" - child["unconfirmed_commits"] = {} - state["reconciliations"][rid] = { - "reconciliation_id": rid, - "context_id": transition["context_id"], - "child_context_id": child["context_id"], - "common_export_root": p["common_export_root"], - "result": result, - "accepted_prefix_length": accepted_prefix, - "invalid_code": invalid_code, - "lineage_digest": domain_digest("ASET/ReconciliationLineage/v1", commits), - } - return [rid], code - -def _withdraw_subtree(state: dict[str, Any], context_id: str, direct_lifecycle: str) -> list[str]: - subtree = {context_id} | _context_descendants(state, context_id) - for cid in subtree: - context = state["contexts"][cid] - context["lifecycle"] = direct_lifecycle if cid == context_id else "WITHDRAWN" - context["guarantee_status"] = "TERMINATED" - state["context_aliases"].pop(context["alias"], None) - for authority in state["authorities"].values(): - if authority["context_id"] in subtree and authority["status"] == "ACTIVE": - authority["status"] = "REVOKED" - for permit in state["permits"].values(): - if permit["context_id"] in subtree and permit["status"] == "ACTIVE": - permit["status"] = "TERMINATED_WITH_CONTEXT" - state["normative_dependencies"] = [ - edge for edge in state["normative_dependencies"] - if edge["source_context_id"] not in subtree and edge["target_context_id"] not in subtree - ] - return sorted(subtree) - - -def _handle_membership_withdraw(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - context = state["contexts"][transition["context_id"]] - if context["parent_context_id"] is None: - raise SeedError("ROOT_WITHDRAWAL_FORBIDDEN") - signer = transition["authn"]["signer_principal_id"] - if signer != context["member_principal_id"]: - raise SeedError("WITHDRAWAL_MEMBER_SIGNATURE_REQUIRED") - affected = compute_affected_sibling_set(state, context["parent_context_id"], context["context_id"] ) - if affected != [context["context_id"]]: - raise SeedError("WITHDRAWAL_REDEFINITION_REQUIRED") - withdrawn = _withdraw_subtree(state, context["context_id"], "WITHDRAWN") - wid = artifact_id("withdrawal", transition["transition_id"] ) - state["membership_withdrawals"][wid] = { - "withdrawal_id": wid, - "context_id": context["context_id"], - "parent_context_id": context["parent_context_id"], - "mode": "VOLUNTARY_EXIT", - "member_principal_id": signer, - "reason_digest": transition["payload"]["reason_digest"], - "proposal_digest": None, - "withdrawn_context_ids": withdrawn, - "authorization_proof_digest": transition["authn"]["proof_digest"], - "transition_ref": transition["transition_id"], - } - return [wid] - - -def _handle_context_redefine(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - _require_authority(state, transition, "REDEFINE_CONTEXT") - p = transition["payload"] - proposal = p["proposal"] - if proposal["parent_context_id"] != transition["context_id"]: - raise SeedError("REDEFINITION_PARENT_MISMATCH") - expected_digest = context_redefinition_proposal_digest(proposal) - if p["proposal_digest"] != expected_digest: - raise SeedError("REDEFINITION_PROPOSAL_DIGEST_MISMATCH") - affected = compute_affected_sibling_set( - state, transition["context_id"], proposal["target_context_id"] - ) - replacements = proposal["replacements"] - old_ids = [item["old_context_id"] for item in replacements] - if len(old_ids) != len(set(old_ids)) or set(old_ids) != set(affected): - raise SeedError("REDEFINITION_AFFECTED_SET_MISMATCH") - auths = p["withdrawal_authorizations"] - auth_ids = [item["context_id"] for item in auths] - if len(auth_ids) != len(set(auth_ids)) or set(auth_ids) != set(affected): - raise SeedError("REDEFINITION_AUTHORIZATION_SET_MISMATCH") - auth_by_id = {item["context_id"]: item for item in auths} - for cid in affected: - context = state["contexts"][cid] - auth = auth_by_id[cid] - if auth["member_principal_id"] != context["member_principal_id"]: - raise SeedError("REDEFINITION_MEMBER_MISMATCH") - if auth["proposal_digest"] != expected_digest: - raise SeedError("REDEFINITION_AUTHORIZATION_BINDING_MISMATCH") - replacement_by_old = {item["old_context_id"]: item for item in replacements} - successor_map: dict[str, str] = {} - genesis_payloads: dict[str, dict[str, Any]] = {} - for old_id in affected: - old = state["contexts"][old_id] - item = replacement_by_old[old_id] - local_alias = old["alias"].rsplit("/", 1)[-1] - member_payload = { - "parent_context_id": transition["context_id"], - "member_principal_id": old["member_principal_id"], - "context_kind": old["context_kind"], - "context_genesis_nonce": item["context_genesis_nonce"], - "local_alias": local_alias, - "initial_authorities": copy.deepcopy(item["initial_authorities"]), - "depends_on_context_ids": list(item["depends_on_context_ids"]), - } - new_id = compute_context_id(transition["context_id"], member_genesis_digest(member_payload)) - if new_id in state["contexts"] or new_id in successor_map.values(): - raise SeedError("REDEFINITION_SUCCESSOR_COLLISION") - successor_map[old_id] = new_id - genesis_payloads[old_id] = member_payload - # Validate dependency references against the pre-state and the exact replacement set. - withdrawn_subtrees: set[str] = set(affected) - for old_id in affected: - withdrawn_subtrees.update(_context_descendants(state, old_id)) - for old_id, member_payload in genesis_payloads.items(): - for dep in member_payload["depends_on_context_ids"]: - if dep not in state["contexts"]: - raise SeedError("DEPENDENCY_CONTEXT_UNKNOWN") - if dep in withdrawn_subtrees and dep not in successor_map: - raise SeedError("REDEFINITION_DEPENDENCY_TARGET_WITHDRAWN") - if dep not in successor_map and state["contexts"][dep]["lifecycle"] != "ACTIVE": - raise SeedError("DEPENDENCY_CONTEXT_INACTIVE") - withdrawal_refs: list[str] = [] - artifacts: list[str] = [] - # Atomic mutation begins only after every predicate above has passed. - for index, old_id in enumerate(affected): - old = state["contexts"][old_id] - withdrawn = _withdraw_subtree(state, old_id, "SUPERSEDED") - wid = artifact_id(f"withdrawal-{index}", transition["transition_id"] ) - state["membership_withdrawals"][wid] = { - "withdrawal_id": wid, "context_id": old_id, - "parent_context_id": transition["context_id"], "mode": "REDEFINITION", - "member_principal_id": old["member_principal_id"], "reason_digest": None, - "proposal_digest": expected_digest, "withdrawn_context_ids": withdrawn, - "authorization_proof_digest": auth_by_id[old_id]["proof_digest"], - "transition_ref": transition["transition_id"], - } - withdrawal_refs.append(wid) - artifacts.append(wid) - # Materialize successors and authority bindings. - for old_id in affected: - old = state["contexts"][old_id] - payload = genesis_payloads[old_id] - new_id = successor_map[old_id] - context = { - "context_id": new_id, "parent_context_id": transition["context_id"], - "context_kind": old["context_kind"], "member_principal_id": old["member_principal_id"], - "genesis_digest": member_genesis_digest(payload), "constitution_epoch": 0, - "local_ordinal": 0, "lifecycle": "ACTIVE", "guarantee_status": "CONFIRMED", - "internal_state_root": domain_digest("ASET/EmptyContextState/v1", {"context_id": new_id}), - "export_root": domain_digest("ASET/EmptyContextExport/v1", {"context_id": new_id}), - "last_confirmed_export_root": domain_digest("ASET/EmptyContextExport/v1", {"context_id": new_id}), - "alias": old["alias"], - } - state["contexts"][new_id] = context - state["context_aliases"][context["alias"]] = new_id - artifacts.append(new_id) - for spec in payload["initial_authorities"]: - binding = _binding_from_spec(new_id, spec, transition["transition_id"], 0) - if binding["authority_id"] in state["authorities"]: - raise SeedError("AUTHORITY_ALREADY_EXISTS") - state["authorities"][binding["authority_id"]] = binding - artifacts.append(binding["authority_id"] ) - # Rebuild only dependencies declared by successor definitions, remapping affected peers. - for old_id in affected: - source_new = successor_map[old_id] - for dep in genesis_payloads[old_id]["depends_on_context_ids"]: - target_new = successor_map.get(dep, dep) - edge = { - "source_context_id": source_new, - "target_context_id": target_new, - "dependency_kind": "NORMATIVE", - } - if edge not in state["normative_dependencies"]: - state["normative_dependencies"].append(edge) - rid = artifact_id("redefinition", transition["transition_id"] ) - state["context_redefinitions"][rid] = { - "redefinition_id": rid, "context_id": transition["context_id"], - "target_context_id": proposal["target_context_id"], - "proposal": copy.deepcopy(proposal), - "proposal_digest": expected_digest, "affected_context_ids": affected, - "successor_map": successor_map, "withdrawal_refs": withdrawal_refs, - "transition_ref": transition["transition_id"], - } - return [rid] + artifacts - -def _handle_context_terminate(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - p = transition["payload"] - _require_authority(state, transition, "TERMINATE_CONTEXT") - child = state["contexts"].get(p["child_context_id"]) - if child is None: - raise SeedError("CONTEXT_UNKNOWN") - if child["parent_context_id"] != transition["context_id"]: - raise SeedError("NOT_DIRECT_CHILD_CONTEXT") - verification = state["verifications"].get(p["verification_ref"]) - if verification is None or verification["status"] != "PASS" or verification["result_class"] != "TRUST_LINEAGE_LOST": - raise SeedError("TRUST_LINEAGE_LOSS_NOT_VERIFIED") - observation = state["observations"][verification["observation_ref"]] - if observation.get("claim_subject_context_id") != child["context_id"]: - raise SeedError("TRUST_LINEAGE_LOSS_SUBJECT_MISMATCH") - terminated = {child["context_id"]} | _context_descendants(state, child["context_id"]) - for cid in terminated: - state["contexts"][cid]["lifecycle"] = "TERMINATED" - state["contexts"][cid]["guarantee_status"] = "TERMINATED" - state["context_aliases"].pop(state["contexts"][cid]["alias"], None) - for authority in state["authorities"].values(): - if authority["context_id"] in terminated and authority["status"] == "ACTIVE": - authority["status"] = "REVOKED" - for permit in state["permits"].values(): - if permit["context_id"] in terminated and permit["status"] == "ACTIVE": - permit["status"] = "TERMINATED_WITH_CONTEXT" - return [] - - -def _handle_correction(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - p = transition["payload"] - _require_authority(state, transition, "VERIFY") - if p["target_type"] != "VERIFICATION": - raise SeedError("CORRECTION_TARGET_TYPE_UNSUPPORTED") - target = state["verifications"].get(p["target_ref"]) - if target is None: - raise SeedError("CORRECTION_TARGET_UNKNOWN") - if any(p["target_ref"] in outcome["verification_refs"] for outcome in state["outcomes"].values()): - raise SeedError("CORRECTION_TARGET_FINALIZED") - if target["context_id"] != transition["context_id"]: - raise SeedError("CORRECTION_CONTEXT_MISMATCH") - if any(c["target_ref"] == p["target_ref"] for c in state["corrections"].values()): - raise SeedError("CORRECTION_TARGET_ALREADY_SUPERSEDED") - replacement_ref = p.get("replacement_ref") - if replacement_ref is not None: - if replacement_ref == p["target_ref"]: - raise SeedError("CORRECTION_SELF_REPLACEMENT") - replacement = state["verifications"].get(replacement_ref) - if replacement is None: - raise SeedError("CORRECTION_REPLACEMENT_UNKNOWN") - if replacement["context_id"] != transition["context_id"]: - raise SeedError("CORRECTION_REPLACEMENT_CONTEXT_MISMATCH") - if ( - replacement["permit_ref"] != target["permit_ref"] - or replacement["receipt_ref"] != target["receipt_ref"] - or replacement["observation_ref"] != target["observation_ref"] - ): - raise SeedError("CORRECTION_REPLACEMENT_LINEAGE_MISMATCH") - if _effective_verification_ref(state, replacement_ref) != replacement_ref: - raise SeedError("CORRECTION_REPLACEMENT_NOT_EFFECTIVE") - cid = artifact_id("correction", transition["transition_id"]) - state["corrections"][cid] = { - "correction_id": cid, - "context_id": transition["context_id"], - "target_type": "VERIFICATION", - "target_ref": p["target_ref"], - "replacement_ref": replacement_ref, - "reason_digest": p["reason_digest"], - "corrector_principal_id": transition["authn"]["signer_principal_id"], - } - return [cid] - -def _transfer_task_digest(authority_ref: str, new_holder_principal_id: str) -> str: - return domain_digest("ASET/AuthorityTransferTask/v1", { - "authority_ref": authority_ref, - "new_holder_principal_id": new_holder_principal_id, - }) - - -def _handle_authority_transfer(state: dict[str, Any], transition: dict[str, Any]) -> list[str]: - p = transition["payload"] - _require_authority(state, transition, "TRANSFER_AUTHORITY") - old = state["authorities"].get(p["authority_ref"]) - outcome = state["outcomes"].get(p["outcome_ref"]) - if old is None or old["status"] != "ACTIVE": - raise SeedError("AUTHORITY_NOT_ACTIVE") - if old["context_id"] != transition["context_id"]: - raise SeedError("AUTHORITY_CONTEXT_MISMATCH") - if outcome is None or outcome["outcome_class"] != "POSITIVE": - raise SeedError("TRANSFER_POSITIVE_OUTCOME_REQUIRED") - permit = state["permits"][outcome["permit_ref"]] - if outcome["context_id"] != old["context_id"] or permit["context_id"] != old["context_id"]: - raise SeedError("TRANSFER_ACTION_CONTEXT_MISMATCH") - expected_task = _transfer_task_digest(old["authority_id"], p["new_holder_principal_id"]) - if permit["task_digest"] != expected_task: - raise SeedError("TRANSFER_TASK_MISMATCH") - if permit["delegate_principal_id"] != p["new_holder_principal_id"]: - raise SeedError("TRANSFER_NEW_HOLDER_NOT_DELEGATE") - readiness = state["decisions"].get(permit["readiness_ref"]) - if ( - readiness is None - or readiness["decision_kind"] != "READINESS_ACCEPT_RESPONSIBILITY" - or readiness["issuer_principal_id"] != p["new_holder_principal_id"] - or readiness["subject_principal_id"] != p["new_holder_principal_id"] - or readiness["related_ref"] != old["authority_id"] - or readiness["context_id"] != old["context_id"] - ): - raise SeedError("TRANSFER_RESPONSIBILITY_READINESS_REQUIRED") - old["status"] = "TRANSFERRED" - new_binding = { - "authority_id": "", "context_id": old["context_id"], - "capability_kind": old["capability_kind"], "scope": list(old["scope"]), - "scope_digest": old["scope_digest"], "holder_principal_id": p["new_holder_principal_id"], - "authority_epoch": old["authority_epoch"] + 1, "status": "ACTIVE", - "grant_provenance": transition["transition_id"], - } - new_binding["authority_id"] = _authority_id(new_binding) - state["authorities"][new_binding["authority_id"]] = new_binding - return [new_binding["authority_id"]] - -HANDLERS = { - "MEMBER_CONTEXT_GENESIS": _handle_member_context_genesis, - "DECISION": _handle_decision, - "PERMIT_ISSUE": _handle_permit_issue, - "PERMIT_ATTENUATE": _handle_permit_attenuate, - "PERMIT_USE": _handle_permit_use, - "OBSERVATION": _handle_observation, - "VERIFICATION": _handle_verification, - "OUTCOME": _handle_outcome, - "EXPORT": _handle_export, - "IMPORT": _handle_import, - "GUARANTEE_SUSPEND": _handle_guarantee_suspend, - "PARTITION_LOCAL_TRANSITION": _handle_partition_local_transition, - "MEMBERSHIP_WITHDRAW": _handle_membership_withdraw, - "CONTEXT_REDEFINE": _handle_context_redefine, - "CONTEXT_TERMINATE": _handle_context_terminate, - "CORRECTION": _handle_correction, - "AUTHORITY_TRANSFER": _handle_authority_transfer, -} - - -def apply_transition(state: dict[str, Any], transition: dict[str, Any]) -> dict[str, Any]: - original = copy.deepcopy(state) - try: - digest = _validate_envelope(state, transition) - except SeedError as exc: - if exc.code in {"IDEMPOTENT_REPLAY", "IDEMPOTENT_SUBMISSION_REPLAY"}: - return {"accepted": True, "code": exc.code, "state_changed": False, "state": original, "artifacts": []} - return {"accepted": False, "code": exc.code, "state_changed": False, "state": original, "artifacts": []} - except Exception: - return {"accepted": False, "code": "MALFORMED_TRANSITION", "state_changed": False, "state": original, "artifacts": []} - working = copy.deepcopy(state) - code = "ACCEPTED" - try: - if transition["kind"] == "RECONCILE": - artifacts, code = _handle_reconcile(working, transition) - else: - handler = HANDLERS.get(transition["kind"]) - if handler is None: - raise SeedError("UNSUPPORTED_TRANSITION_KIND") - artifacts = handler(working, transition) - _record_transition(working, transition, digest, artifacts) - validate_state(working) - except SeedError as exc: - if exc.code == "IDEMPOTENT_SUBMISSION_REPLAY": - return {"accepted": True, "code": exc.code, "state_changed": False, "state": original, "artifacts": []} - return {"accepted": False, "code": exc.code, "state_changed": False, "state": original, "artifacts": []} - except Exception: - return {"accepted": False, "code": "MALFORMED_TRANSITION", "state_changed": False, "state": original, "artifacts": []} - return {"accepted": True, "code": code, "state_changed": True, "state": working, "artifacts": artifacts} - -def validate_case(case: dict[str, Any]) -> tuple[bool, dict[str, Any], dict[str, Any]]: - try: - state = initialize_state(copy.deepcopy(case["initial_genesis"])) - except SeedError as exc: - actual = {"accepted": False, "code": exc.code, "state_changed": False} - expected = case["expected"] - return actual == expected, actual, expected - for setup in case.get("setup", []): - result = apply_transition(state, setup) - if not result["accepted"] or not result["state_changed"]: - actual = {"accepted": False, "code": "SETUP_FAILED:" + result["code"], "state_changed": False} - expected = case["expected"] - return False, actual, expected - state = result["state"] - result = apply_transition(state, case["candidate"]) - actual = { - "accepted": result["accepted"], - "code": result["code"], - "state_changed": result["state_changed"], - } - expected = case["expected"] - ok = actual == expected - for assertion in case.get("postconditions", []): - if not result["accepted"]: - ok = False - break - cursor: Any = result["state"] - for part in assertion["path"].split("/")[1:]: - cursor = cursor[int(part)] if isinstance(cursor, list) else cursor[part] - if cursor != assertion["equals"]: - ok = False - return ok, actual, expected - - -__all__ = [ - "VERSION", "SEED_SEMANTICS_ID", "IMPLEMENTATION_VERSION", "SeedError", "canonical_bytes", "domain_digest", - "constitution_digest", "compute_root_genesis_digest", "compute_context_id", - "compute_trust_space_id", "member_genesis_digest", "transition_digest", - "compute_transition_id", "compute_state_root", "artifact_id", "scope_digest", "permit_terms_digest", - "compute_affected_sibling_set", "context_redefinition_proposal_digest", "_causal_basis_refs", "_required_causal_parents", "initialize_state", "apply_transition", "validate_state", - "validate_case", "_transfer_task_digest", "compute_local_commit_id", "_local_commit_root", -] diff --git a/src/aset_python_sqlite/kernel.py b/src/aset_python_sqlite/kernel.py new file mode 100644 index 0000000..250529b --- /dev/null +++ b/src/aset_python_sqlite/kernel.py @@ -0,0 +1,317 @@ +from __future__ import annotations + +import copy +import hashlib +import json +from dataclasses import dataclass +from typing import Any, Iterable + +RESOLUTIONS = frozenset({"UNKNOWN", "ALLOW", "BLOCK"}) +TERMINAL_RESOLUTIONS = frozenset({"ALLOW", "BLOCK"}) +OPERATIONS = frozenset({"REGISTER_REQUEST", "SUBMIT_RESOLUTION", "EVALUATE_RESOLUTION"}) + + +def canonical_bytes(value: Any) -> bytes: + return json.dumps( + value, + ensure_ascii=False, + sort_keys=True, + separators=(",", ":"), + allow_nan=False, + ).encode("utf-8") + + +def digest_value(value: Any) -> str: + return "sha256:" + hashlib.sha256(canonical_bytes(value)).hexdigest() + + +def without(value: dict[str, Any], key: str) -> dict[str, Any]: + return {name: item for name, item in value.items() if name != key} + + +def valid_digest(value: dict[str, Any], field: str) -> bool: + candidate = value.get(field) + return isinstance(candidate, str) and candidate == digest_value(without(value, field)) + + +def empty_store() -> dict[str, list[dict[str, Any]]]: + return {"requests": [], "records": [], "authority_bindings": []} + + +def normalize_store(value: dict[str, Any]) -> dict[str, list[dict[str, Any]]]: + if set(value) != {"requests", "records", "authority_bindings"}: + raise ValueError("store must contain exactly requests, records, and authority_bindings") + normalized: dict[str, list[dict[str, Any]]] = {} + for key in ("requests", "records", "authority_bindings"): + items = value.get(key) + if not isinstance(items, list) or not all(isinstance(item, dict) for item in items): + raise ValueError(f"store.{key} must be an array of objects") + normalized[key] = copy.deepcopy(items) + canonical_bytes(normalized) + return normalized + + +def binding_valid(binding: dict[str, Any]) -> bool: + return valid_digest(binding, "binding_digest") + + +def authority_binding_valid(binding: dict[str, Any]) -> bool: + return valid_digest(binding, "authority_binding_digest") + + +def request_valid(request: dict[str, Any]) -> bool: + binding = request.get("binding") + return ( + isinstance(binding, dict) + and binding_valid(binding) + and valid_digest(request, "request_digest") + ) + + +def record_digest_valid(record: dict[str, Any]) -> bool: + return valid_digest(record, "record_digest") + + +@dataclass(frozen=True) +class AuthorityVerdict: + valid: bool + reason: str + + +def authority_recognition_valid( + store: dict[str, list[dict[str, Any]]], + request: dict[str, Any], + record: dict[str, Any], +) -> AuthorityVerdict: + bindings = [item for item in store["authority_bindings"] if authority_binding_valid(item)] + by_digest = {item["authority_binding_digest"]: item for item in bindings} + root = by_digest.get(request["initial_authority_binding_digest"]) + if root is None: + return AuthorityVerdict(False, "LOCAL_AUTHORITY_BINDING_INVALID") + + binding = request["binding"] + exact = ( + root.get("context_id") == binding.get("context_id") + and root.get("policy_epoch") == binding.get("policy_epoch") + and root.get("binding_digest") == binding.get("binding_digest") + ) + if not exact: + return AuthorityVerdict(False, "LOCAL_AUTHORITY_BINDING_MISMATCH") + + target = record.get("authority_id") + target_bindings = [item for item in bindings if item.get("authority_id") == target] + if not target_bindings: + return AuthorityVerdict(False, "TERMINAL_AUTHORITY_UNRECOGNIZED") + if not any( + item.get("context_id") == binding.get("context_id") + and item.get("policy_epoch") == binding.get("policy_epoch") + and item.get("binding_digest") == binding.get("binding_digest") + for item in target_bindings + ): + return AuthorityVerdict(False, "TERMINAL_AUTHORITY_BINDING_MISMATCH") + return AuthorityVerdict(True, "EXACT_BINDING_AUTHORITY_RECOGNIZED") + + +def record_valid( + store: dict[str, list[dict[str, Any]]], + request: dict[str, Any], + record: dict[str, Any], +) -> tuple[bool, str]: + if not record_digest_valid(record): + return False, "RECORD_DIGEST_INVALID" + if record.get("resolution") not in TERMINAL_RESOLUTIONS: + return False, "TERMINAL_RESOLUTION_INVALID" + if record.get("resolution_id") != request.get("resolution_id"): + return False, "RESOLUTION_ID_MISMATCH" + if record.get("request_digest") != request.get("request_digest"): + return False, "REQUEST_DIGEST_MISMATCH" + if record.get("binding_digest") != request["binding"].get("binding_digest"): + return False, "BINDING_MISMATCH" + evidence = record.get("authority_evidence_digests") + if not isinstance(evidence, list) or not all(isinstance(item, str) for item in evidence): + return False, "AUTHORITY_EVIDENCE_INVALID" + authority = authority_recognition_valid(store, request, record) + if not authority.valid: + return False, authority.reason + return True, authority.reason + + +def evaluate(store: dict[str, list[dict[str, Any]]], resolution_id: str) -> dict[str, Any]: + requests = [item for item in store["requests"] if item.get("resolution_id") == resolution_id] + if len(requests) != 1 or not request_valid(requests[0]): + return { + "resolution_id": resolution_id, + "resolution": "UNKNOWN", + "effect_permitted": False, + "reason": "REQUEST_NOT_FOUND_OR_INVALID", + "terminal_record_digest": None, + } + + request = requests[0] + valid: list[dict[str, Any]] = [] + invalid_present = False + for record in store["records"]: + if record.get("resolution_id") != resolution_id: + continue + ok, _ = record_valid(store, request, record) + if ok: + valid.append(record) + else: + invalid_present = True + + valid = list({item["record_digest"]: item for item in valid}.values()) + if len(valid) == 1: + record = valid[0] + return { + "resolution_id": resolution_id, + "resolution": record["resolution"], + "effect_permitted": record["resolution"] == "ALLOW", + "reason": "UNIQUE_VALID_TERMINAL_RECORD", + "terminal_record_digest": record["record_digest"], + } + if len(valid) > 1: + reason = "CONFLICTING_TERMINAL_RECORDS" + elif invalid_present: + reason = "NO_VALID_TERMINAL_RECORD" + else: + reason = "TERMINAL_RECORD_ABSENT" + return { + "resolution_id": resolution_id, + "resolution": "UNKNOWN", + "effect_permitted": False, + "reason": reason, + "terminal_record_digest": None, + } + + +def _actual(accepted: bool, code: str, state_changed: bool, evaluation: dict[str, Any]) -> dict[str, Any]: + return { + "accepted": accepted, + "code": code, + "state_changed": state_changed, + "resolution": evaluation["resolution"], + "effect_permitted": evaluation["effect_permitted"], + "reason": evaluation["reason"], + } + + +def recognized_terminal_record_digests( + store: dict[str, Any], + externally_recognized: Iterable[str] = (), +) -> set[str]: + recognized = {item for item in externally_recognized if isinstance(item, str)} + requests_by_id: dict[str, list[dict[str, Any]]] = {} + for request in store.get("requests", []): + resolution_id = request.get("resolution_id") + if isinstance(resolution_id, str): + requests_by_id.setdefault(resolution_id, []).append(request) + for record in store.get("records", []): + resolution_id = record.get("resolution_id") + candidates = requests_by_id.get(resolution_id, []) + if len(candidates) != 1 or not request_valid(candidates[0]): + continue + ok, _ = record_valid(store, candidates[0], record) + record_digest = record.get("record_digest") + if ok and isinstance(record_digest, str): + recognized.add(record_digest) + return recognized + + +def register_request( + store: dict[str, list[dict[str, Any]]], + request: dict[str, Any], + externally_recognized: Iterable[str] = (), +) -> tuple[dict[str, Any], dict[str, list[dict[str, Any]]]]: + resolution_id = str(request.get("resolution_id", "invalid-resolution")) + before = copy.deepcopy(store) + if not request_valid(request): + return _actual(False, "REQUEST_INVALID", False, evaluate(before, resolution_id)), before + if any(item.get("resolution_id") == resolution_id for item in store["requests"]): + return _actual(False, "RESOLUTION_ID_NOT_FRESH", False, evaluate(before, resolution_id)), before + + bindings = { + item["authority_binding_digest"]: item + for item in store["authority_bindings"] + if authority_binding_valid(item) + } + authority = bindings.get(request["initial_authority_binding_digest"]) + if authority is None: + return _actual(False, "LOCAL_AUTHORITY_BINDING_INVALID", False, evaluate(before, resolution_id)), before + binding = request["binding"] + if ( + authority.get("context_id") != binding.get("context_id") + or authority.get("policy_epoch") != binding.get("policy_epoch") + or authority.get("binding_digest") != binding.get("binding_digest") + ): + return _actual(False, "LOCAL_AUTHORITY_BINDING_MISMATCH", False, evaluate(before, resolution_id)), before + + previous_digest = request.get("previous_terminal_record_digest") + if previous_digest is not None: + if previous_digest not in recognized_terminal_record_digests(store, externally_recognized): + return _actual( + False, + "PREVIOUS_TERMINAL_COMMITMENT_UNRECOGNIZED", + False, + evaluate(before, resolution_id), + ), before + + after = copy.deepcopy(store) + after["requests"].append(copy.deepcopy(request)) + return _actual(True, "REQUEST_REGISTERED", True, evaluate(after, resolution_id)), after + + +def submit_resolution( + store: dict[str, list[dict[str, Any]]], + record: dict[str, Any], +) -> tuple[dict[str, Any], dict[str, list[dict[str, Any]]]]: + resolution_id = str(record.get("resolution_id", "invalid-resolution")) + before = copy.deepcopy(store) + requests = [item for item in store["requests"] if item.get("resolution_id") == resolution_id] + if len(requests) != 1 or not request_valid(requests[0]): + return _actual(False, "REQUEST_NOT_FOUND_OR_INVALID", False, evaluate(before, resolution_id)), before + request = requests[0] + ok, reason = record_valid(store, request, record) + if not ok: + return _actual(False, reason, False, evaluate(before, resolution_id)), before + + existing = [item for item in store["records"] if item.get("resolution_id") == resolution_id] + if any(item.get("record_digest") == record.get("record_digest") for item in existing): + return _actual(True, "IDEMPOTENT_REPLAY", False, evaluate(before, resolution_id)), before + if existing: + return _actual(False, "TERMINAL_IMMUTABLE", False, evaluate(before, resolution_id)), before + + after = copy.deepcopy(store) + after["records"].append(copy.deepcopy(record)) + return _actual(True, "RESOLUTION_RECORDED", True, evaluate(after, resolution_id)), after + + +def execute_operation( + store: dict[str, list[dict[str, Any]]], + operation: dict[str, Any], + externally_recognized: Iterable[str] = (), +) -> tuple[dict[str, Any], dict[str, list[dict[str, Any]]]]: + store = normalize_store(store) + kind = operation.get("kind") + payload = operation.get("payload") + if not isinstance(payload, dict): + return _actual(False, "OPERATION_INVALID", False, evaluate(store, "invalid-resolution")), store + + if kind == "REGISTER_REQUEST": + request = payload.get("request") + if not isinstance(request, dict): + return _actual(False, "OPERATION_INVALID", False, evaluate(store, "invalid-resolution")), store + return register_request(store, request, externally_recognized) + + if kind == "SUBMIT_RESOLUTION": + record = payload.get("record") + if not isinstance(record, dict): + return _actual(False, "OPERATION_INVALID", False, evaluate(store, "invalid-resolution")), store + return submit_resolution(store, record) + + if kind == "EVALUATE_RESOLUTION": + resolution_id = payload.get("resolution_id") + if not isinstance(resolution_id, str): + return _actual(False, "OPERATION_INVALID", False, evaluate(store, "invalid-resolution")), store + return _actual(True, "EVALUATED", False, evaluate(store, resolution_id)), store + + return _actual(False, "OPERATION_UNKNOWN", False, evaluate(store, "invalid-resolution")), store diff --git a/src/aset_python_sqlite/proofs.py b/src/aset_python_sqlite/proofs.py deleted file mode 100644 index 0e68a03..0000000 --- a/src/aset_python_sqlite/proofs.py +++ /dev/null @@ -1,86 +0,0 @@ - -from __future__ import annotations - -import base64 -import copy -import hashlib -import hmac -from collections.abc import Mapping -from dataclasses import dataclass -from typing import Protocol - -from .core import canonical_bytes, compute_transition_id - - -class ProofVerifier(Protocol): - profile_id: str - - def verify(self, transition: dict) -> bool: - """Return True only when the transition proof is accepted.""" - - -@dataclass(frozen=True) -class RejectAllProofVerifier: - profile_id: str = "REJECT_ALL" - - def verify(self, transition: dict) -> bool: - return False - - -def proof_material(transition: dict) -> bytes: - material = copy.deepcopy(transition) - material.pop("transition_id", None) - authn = material.get("authn") - if isinstance(authn, dict): - authn.pop("proof_digest", None) - return canonical_bytes(material) - - -@dataclass(frozen=True) -class HmacSha256ProofVerifier: - secrets: Mapping[str, bytes] - profile_id: str = "HMAC_SHA256_V1" - - @classmethod - def from_base64(cls, secrets: Mapping[str, str]) -> HmacSha256ProofVerifier: - decoded: dict[str, bytes] = {} - for principal, encoded in secrets.items(): - if not isinstance(principal, str) or not principal: - raise ValueError("proof principal identifiers must be non-empty strings") - secret = base64.b64decode(encoded, validate=True) - if len(secret) < 32: - raise ValueError("HMAC secrets must contain at least 32 bytes") - decoded[principal] = secret - return cls(decoded) - - def verify(self, transition: dict) -> bool: - authn = transition.get("authn") - if not isinstance(authn, dict): - return False - principal = authn.get("signer_principal_id") - claimed = authn.get("proof_digest") - if not isinstance(principal, str) or not isinstance(claimed, str): - return False - secret = self.secrets.get(principal) - if secret is None or len(secret) < 32: - return False - expected = "sha256:" + hmac.new( - secret, - proof_material(transition), - hashlib.sha256, - ).hexdigest() - return hmac.compare_digest(expected, claimed) - - -def sign_transition_hmac(transition: dict, secret: bytes) -> dict: - if len(secret) < 32: - raise ValueError("HMAC secret must contain at least 32 bytes") - signed = copy.deepcopy(transition) - authn = signed.setdefault("authn", {}) - authn["proof_digest"] = "sha256:" + hmac.new( - secret, - proof_material(signed), - hashlib.sha256, - ).hexdigest() - signed["transition_id"] = compute_transition_id(signed) - return signed diff --git a/src/aset_python_sqlite/runtime.py b/src/aset_python_sqlite/runtime.py index e172ff3..e71fff1 100644 --- a/src/aset_python_sqlite/runtime.py +++ b/src/aset_python_sqlite/runtime.py @@ -1,406 +1,263 @@ from __future__ import annotations -import copy -import hashlib import json -import re from dataclasses import dataclass -from datetime import UTC, datetime +from datetime import datetime, timezone from pathlib import Path -from typing import Any +from typing import Any, Iterable -from . import core -from .jsonio import dumps_canonical, loads_strict -from .proofs import ProofVerifier, RejectAllProofVerifier -from .store import PROFILE_ID, SqliteStore, StoreError +from . import kernel +from .jsonio import dumps_canonical +from .seed_binding import expected_store_metadata +from .store import SqliteStore, StoreError -MAX_TRANSITION_BYTES = 8 * 1024 * 1024 ZERO_HASH = "sha256:" + "0" * 64 -TRUST_SPACE_ID_PATTERN = re.compile(r"^ts:[0-9a-f]{64}$") @dataclass(frozen=True) -class RuntimeStatus: - profile_id: str - implementation_version: str - wire_version: str - seed_semantics_id: str - proof_profile: str +class RuntimeHealth: database_integrity: str - state_validation: str + seed_binding: str + store_validation: str audit_chain: str -def _rejection(code: str) -> dict[str, Any]: - return { - "accepted": False, - "code": code, - "state_changed": False, - "artifacts": [], - } +def _now() -> str: + return datetime.now(timezone.utc).isoformat(timespec="microseconds").replace("+00:00", "Z") -class DurableSeedRuntime: - def __init__( - self, - database: Path, - *, - proof_verifier: ProofVerifier | None = None, - busy_timeout_ms: int = 5000, - ) -> None: - self.store = SqliteStore(database, busy_timeout_ms=busy_timeout_ms) - self.proof_verifier = proof_verifier or RejectAllProofVerifier() +def _decode_store(text: str, digest: str) -> dict[str, list[dict[str, Any]]]: + try: + value = json.loads(text) + except json.JSONDecodeError as error: + raise StoreError("stored Seed store is not valid JSON") from error + if not isinstance(value, dict): + raise StoreError("stored Seed store must be an object") + try: + store = kernel.normalize_store(value) + except (TypeError, ValueError) as error: + raise StoreError("stored Seed store validation failed") from error + if kernel.digest_value(store) != digest: + raise StoreError("stored Seed store digest mismatch") + return store + - @staticmethod - def _now() -> str: - return datetime.now(UTC).isoformat(timespec="microseconds").replace("+00:00", "Z") +class DurableSeedRuntime: + """Non-normative SQLite durability profile bound to one released ASET Seed.""" - @staticmethod - def _trust_space_id_is_valid(value: object) -> bool: - return isinstance(value, str) and TRUST_SPACE_ID_PATTERN.fullmatch(value) is not None + def __init__(self, path: Path, *, busy_timeout_ms: int = 5000) -> None: + self.store = SqliteStore(path, busy_timeout_ms=busy_timeout_ms) + self._require_seed_binding() - @staticmethod - def _decode_stored_state(row) -> dict[str, Any]: - try: - text = row["state_json"] - state = loads_strict( - text, - max_bytes=max(1, len(text.encode("utf-8"))), - ) - if not isinstance(state, dict): - raise ValueError("stored state is not an object") - core.validate_state(state) - if state["trust_space_id"] != row["trust_space_id"]: - raise ValueError("stored trust-space identity mismatch") - if state["current_state_root"] != row["state_root"]: - raise ValueError("stored state-root mismatch") - except (KeyError, TypeError, ValueError, UnicodeError, core.SeedError) as error: - raise StoreError("stored state validation failed") from error - return state + def _require_seed_binding(self) -> None: + metadata = self.store.metadata() + for key, value in expected_store_metadata().items(): + if metadata.get(key) != value: + raise StoreError(f"database seed binding mismatch for {key}") - def initialize(self, genesis: dict[str, Any]) -> dict[str, Any]: - state = core.initialize_state(copy.deepcopy(genesis)) - trust_space_id = state["trust_space_id"] - state_json = dumps_canonical(state) - now = self._now() + def initialize(self, initial_store: dict[str, Any] | None = None) -> dict[str, list[dict[str, Any]]]: + normalized = kernel.normalize_store(initial_store or kernel.empty_store()) + store_json = dumps_canonical(normalized) + store_digest = kernel.digest_value(normalized) + created_at = _now() with self.store.transaction() as connection: - existing = connection.execute( - """ - SELECT trust_space_id, state_json, state_root - FROM trust_spaces WHERE trust_space_id=? - """, - (trust_space_id,), + row = connection.execute( + "SELECT store_json, store_digest FROM seed_store WHERE singleton=1" ).fetchone() - if existing is not None: - existing_state = self._decode_stored_state(existing) - if existing["state_root"] != state["current_state_root"]: - raise StoreError("trust space already exists with a different root") - return existing_state - connection.execute( - """ - INSERT INTO trust_spaces( - trust_space_id, state_json, state_root, revision, created_at, updated_at - ) VALUES (?, ?, ?, 0, ?, ?) - """, - (trust_space_id, state_json, state["current_state_root"], now, now), - ) - return state + if row is None: + connection.execute( + """ + INSERT INTO seed_store( + singleton, store_json, store_digest, revision, created_at, updated_at + ) VALUES (1, ?, ?, 0, ?, ?) + """, + (store_json, store_digest, created_at, created_at), + ) + return normalized + existing = _decode_store(row["store_json"], row["store_digest"]) + if row["store_digest"] != store_digest: + raise StoreError("runtime already initialized with a different Seed store") + return existing - def get_state(self, trust_space_id: str) -> dict[str, Any]: - if not self._trust_space_id_is_valid(trust_space_id): - raise StoreError("trust space identifier is invalid") + def get_store(self) -> dict[str, list[dict[str, Any]]]: connection = self.store.connect() try: row = connection.execute( - """ - SELECT trust_space_id, state_json, state_root - FROM trust_spaces WHERE trust_space_id=? - """, - (trust_space_id,), + "SELECT store_json, store_digest FROM seed_store WHERE singleton=1" ).fetchone() finally: connection.close() if row is None: - raise StoreError("trust space is unknown") - return self._decode_stored_state(row) + raise StoreError("runtime is not initialized") + return _decode_store(row["store_json"], row["store_digest"]) - def _last_audit_hash(self, connection, trust_space_id: str) -> str: - row = connection.execute( - """ - SELECT entry_hash FROM transition_attempts - WHERE trust_space_id=? ORDER BY sequence DESC LIMIT 1 - """, - (trust_space_id,), - ).fetchone() - return ZERO_HASH if row is None else row["entry_hash"] + def evaluate(self, resolution_id: str) -> dict[str, Any]: + if not isinstance(resolution_id, str): + raise TypeError("resolution_id must be a string") + return kernel.evaluate(self.get_store(), resolution_id) - def _record_attempt( + def execute( self, - connection, + operation: dict[str, Any], *, - trust_space_id: str, - transition: dict[str, Any], - result: dict[str, Any], - before_root: str, - after_root: str, - created_at: str, - ) -> None: - transition_json = dumps_canonical(transition) - result_record = { - "accepted": bool(result["accepted"]), - "code": str(result["code"]), - "state_changed": bool(result["state_changed"]), - "artifacts": list(result.get("artifacts", [])), - } - result_json = dumps_canonical(result_record) - previous = self._last_audit_hash(connection, trust_space_id) - entry_material = { - "trust_space_id": trust_space_id, - "transition_id": transition.get("transition_id", "MISSING"), - "transition_json": transition_json, - "result_json": result_json, - "before_state_root": before_root, - "after_state_root": after_root, - "proof_profile": self.proof_verifier.profile_id, - "previous_entry_hash": previous, - "created_at": created_at, - } - entry_hash = core.domain_digest("ASET/RuntimeAuditEntry/v1", entry_material) - connection.execute( - """ - INSERT INTO transition_attempts( - trust_space_id, transition_id, transition_json, result_json, - before_state_root, after_state_root, accepted, state_changed, - code, proof_profile, previous_entry_hash, entry_hash, created_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - trust_space_id, - transition.get("transition_id", "MISSING"), - transition_json, - result_json, - before_root, - after_root, - int(bool(result["accepted"])), - int(bool(result["state_changed"])), - result["code"], - self.proof_verifier.profile_id, - previous, - entry_hash, - created_at, - ), - ) - - def apply(self, trust_space_id: object, transition: object) -> dict[str, Any]: - if not self._trust_space_id_is_valid(trust_space_id): - return _rejection("TRUST_SPACE_ID_INVALID") + recognized_terminal_record_digests: Iterable[str] = (), + ) -> dict[str, Any]: try: - serialized = dumps_canonical(transition).encode("utf-8") - except (TypeError, ValueError, UnicodeError): - # A non-JSON Python object is not a transition document and cannot be - # represented safely in the normative transition audit chain. - return _rejection("INPUT_NOT_JSON_VALUE") + operation_json = dumps_canonical(operation) + recognized = sorted( + {item for item in recognized_terminal_record_digests if isinstance(item, str)} + ) + environment_json = dumps_canonical( + {"recognized_terminal_record_digests": recognized} + ) + except (TypeError, ValueError) as error: + raise ValueError("operation and environment must be strict JSON values") from error - now = self._now() with self.store.transaction() as connection: row = connection.execute( - """ - SELECT trust_space_id, state_json, state_root, revision - FROM trust_spaces WHERE trust_space_id=? - """, - (trust_space_id,), + "SELECT store_json, store_digest, revision FROM seed_store WHERE singleton=1" ).fetchone() if row is None: - # No trust-space-local audit chain exists for this identifier. - return _rejection("TRUST_SPACE_UNKNOWN") + raise StoreError("runtime is not initialized") + before = _decode_store(row["store_json"], row["store_digest"]) + before_digest = row["store_digest"] + result, after = kernel.execute_operation(before, operation, recognized) + after_json = dumps_canonical(after) + after_digest = kernel.digest_value(after) - before_root = row["state_root"] - audit_transition = transition - if len(serialized) > MAX_TRANSITION_BYTES: - audit_transition = { - "document_type": "aset-seed-oversized-transition-reference", - "transition_id": ( - transition.get("transition_id", "MISSING") - if isinstance(transition, dict) - else "MISSING" - ), - "sha256": "sha256:" + hashlib.sha256(serialized).hexdigest(), - "size_bytes": len(serialized), - } - result = _rejection("TRANSITION_TOO_LARGE") - elif not isinstance(transition, dict): - audit_transition = { - "document_type": "aset-seed-malformed-transition-reference", - "sha256": "sha256:" + hashlib.sha256(serialized).hexdigest(), - "size_bytes": len(serialized), - } - result = _rejection("MALFORMED_TRANSITION") - else: - try: - state = self._decode_stored_state(row) - except StoreError: - result = _rejection("STORED_STATE_INVALID") - else: - try: - core.validate_transition(transition) - except core.SeedError as error: - result = _rejection(error.code) - else: - try: - proof_accepted = self.proof_verifier.verify(transition) is True - except Exception: - result = _rejection("PROOF_VERIFIER_ERROR") - else: - if not proof_accepted: - result = _rejection("PROOF_REJECTED") - else: - result = core.apply_transition( - state, - copy.deepcopy(transition), - ) - - after_root = before_root - if result["accepted"] and result["state_changed"]: - new_state = result["state"] - after_root = new_state["current_state_root"] - cursor = connection.execute( + if bool(result["state_changed"]): + connection.execute( """ - UPDATE trust_spaces - SET state_json=?, state_root=?, revision=?, updated_at=? - WHERE trust_space_id=? AND revision=? + UPDATE seed_store + SET store_json=?, store_digest=?, revision=?, updated_at=? + WHERE singleton=1 """, - ( - dumps_canonical(new_state), - after_root, - row["revision"] + 1, - now, - trust_space_id, - row["revision"], - ), + (after_json, after_digest, int(row["revision"]) + 1, _now()), ) - if cursor.rowcount != 1: - raise StoreError("serialized state update failed") - result = {key: value for key, value in result.items() if key != "state"} - self._record_attempt( - connection, - trust_space_id=trust_space_id, - transition=audit_transition, - result=result, - before_root=before_root, - after_root=after_root, - created_at=now, - ) - return result + elif after_digest != before_digest: + raise StoreError("Seed kernel changed store while reporting state_changed=false") - def validate(self, trust_space_id: str) -> None: - core.validate_state(self.get_state(trust_space_id)) + previous = connection.execute( + "SELECT entry_hash FROM operation_attempts ORDER BY sequence DESC LIMIT 1" + ).fetchone() + previous_hash = previous["entry_hash"] if previous is not None else ZERO_HASH + created_at = _now() + result_json = dumps_canonical(result) + material = { + "operation_json": operation_json, + "environment_json": environment_json, + "result_json": result_json, + "before_store_digest": before_digest, + "after_store_digest": after_digest, + "previous_entry_hash": previous_hash, + "created_at": created_at, + } + entry_hash = kernel.digest_value(material) + connection.execute( + """ + INSERT INTO operation_attempts( + operation_json, environment_json, result_json, + before_store_digest, after_store_digest, + state_changed, code, previous_entry_hash, entry_hash, created_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + operation_json, + environment_json, + result_json, + before_digest, + after_digest, + int(bool(result["state_changed"])), + result["code"], + previous_hash, + entry_hash, + created_at, + ), + ) + return result - def verify_audit_chain(self, trust_space_id: str) -> bool: + def verify_audit_chain(self) -> bool: connection = self.store.connect() try: rows = connection.execute( - """ - SELECT * FROM transition_attempts - WHERE trust_space_id=? ORDER BY sequence - """, - (trust_space_id,), + "SELECT * FROM operation_attempts ORDER BY sequence" ).fetchall() + store_row = connection.execute( + "SELECT store_digest, revision FROM seed_store WHERE singleton=1" + ).fetchone() finally: connection.close() - previous = ZERO_HASH + if store_row is None: + return False + + previous_hash = ZERO_HASH changed_count = 0 - last_after_root: str | None = None + last_after_digest: str | None = None for row in rows: - if row["previous_entry_hash"] != previous: + if row["previous_entry_hash"] != previous_hash: return False try: result = json.loads(row["result_json"]) + json.loads(row["operation_json"]) + json.loads(row["environment_json"]) except json.JSONDecodeError: return False - if ( - bool(row["accepted"]) != bool(result.get("accepted")) - or bool(row["state_changed"]) != bool(result.get("state_changed")) - or row["code"] != result.get("code") - ): + if bool(row["state_changed"]) != bool(result.get("state_changed")): + return False + if row["code"] != result.get("code"): return False material = { - "trust_space_id": row["trust_space_id"], - "transition_id": row["transition_id"], - "transition_json": row["transition_json"], + "operation_json": row["operation_json"], + "environment_json": row["environment_json"], "result_json": row["result_json"], - "before_state_root": row["before_state_root"], - "after_state_root": row["after_state_root"], - "proof_profile": row["proof_profile"], + "before_store_digest": row["before_store_digest"], + "after_store_digest": row["after_store_digest"], "previous_entry_hash": row["previous_entry_hash"], "created_at": row["created_at"], } - expected = core.domain_digest("ASET/RuntimeAuditEntry/v1", material) - if expected != row["entry_hash"]: + if kernel.digest_value(material) != row["entry_hash"]: + return False + if not row["state_changed"] and row["before_store_digest"] != row["after_store_digest"]: return False changed_count += int(bool(row["state_changed"])) - last_after_root = row["after_state_root"] - previous = row["entry_hash"] + last_after_digest = row["after_store_digest"] + previous_hash = row["entry_hash"] - connection = self.store.connect() - try: - state_row = connection.execute( - "SELECT state_root, revision FROM trust_spaces WHERE trust_space_id=?", - (trust_space_id,), - ).fetchone() - finally: - connection.close() - if state_row is None: - return False - if changed_count != state_row["revision"]: + if changed_count != int(store_row["revision"]): return False - return last_after_root is None or last_after_root == state_row["state_root"] + return last_after_digest is None or last_after_digest == store_row["store_digest"] - def _validate_stored_states(self) -> bool: - connection = self.store.connect() - try: - rows = connection.execute( - "SELECT trust_space_id, state_json, state_root FROM trust_spaces" - ).fetchall() - finally: - connection.close() - for row in rows: - try: - self._decode_stored_state(row) - except StoreError: - return False - return True - - def health(self) -> RuntimeStatus: + def health(self) -> RuntimeHealth: connection = self.store.connect() try: integrity = connection.execute("PRAGMA integrity_check").fetchone()[0] - spaces = [ - row[0] - for row in connection.execute( - "SELECT trust_space_id FROM trust_spaces" - ) - ] finally: connection.close() - state_ok = self._validate_stored_states() - audit_ok = all(self.verify_audit_chain(space) for space in spaces) - return RuntimeStatus( - profile_id=PROFILE_ID, - implementation_version=core.IMPLEMENTATION_VERSION, - wire_version=core.VERSION, - seed_semantics_id=core.SEED_SEMANTICS_ID, - proof_profile=self.proof_verifier.profile_id, + try: + self._require_seed_binding() + binding = "PASS" + except StoreError: + binding = "FAIL" + try: + self.get_store() + store_validation = "PASS" + except StoreError: + store_validation = "FAIL" + audit = "PASS" if self.verify_audit_chain() else "FAIL" + return RuntimeHealth( database_integrity=str(integrity), - state_validation="PASS" if state_ok else "FAIL", - audit_chain="PASS" if audit_ok else "FAIL", + seed_binding=binding, + store_validation=store_validation, + audit_chain=audit, ) def backup(self, destination: Path) -> None: - status = self.health() + health = self.health() if ( - status.database_integrity != "ok" - or status.state_validation != "PASS" - or status.audit_chain != "PASS" + health.database_integrity != "ok" + or health.seed_binding != "PASS" + or health.store_validation != "PASS" + or health.audit_chain != "PASS" ): - raise StoreError("backup refused because runtime health validation failed") + raise StoreError("runtime health validation failed before backup") self.store.backup(destination) diff --git a/src/aset_python_sqlite/schemas/authority-binding.schema.json b/src/aset_python_sqlite/schemas/authority-binding.schema.json deleted file mode 100644 index aeac0c1..0000000 --- a/src/aset_python_sqlite/schemas/authority-binding.schema.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/authority-binding.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "authority_epoch": { - "minimum": 0, - "type": "integer" - }, - "authority_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "capability_kind": { - "minLength": 1, - "type": "string" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "grant_provenance": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "holder_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "scope": { - "items": { - "maxLength": 120, - "minLength": 1, - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "scope_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "status": { - "enum": [ - "ACTIVE", - "TRANSFERRED", - "REVOKED", - "SUSPENDED" - ] - } - }, - "required": [ - "authority_id", - "context_id", - "capability_kind", - "scope", - "scope_digest", - "holder_principal_id", - "authority_epoch", - "status", - "grant_provenance" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/common.schema.json b/src/aset_python_sqlite/schemas/common.schema.json deleted file mode 100644 index 65d552d..0000000 --- a/src/aset_python_sqlite/schemas/common.schema.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "$defs": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "nonNegativeInt": { - "minimum": 0, - "type": "integer" - }, - "positiveInt": { - "minimum": 1, - "type": "integer" - }, - "scope": { - "items": { - "maxLength": 120, - "minLength": 1, - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - } - }, - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema" -} diff --git a/src/aset_python_sqlite/schemas/conformance-case.schema.json b/src/aset_python_sqlite/schemas/conformance-case.schema.json deleted file mode 100644 index f3c98f1..0000000 --- a/src/aset_python_sqlite/schemas/conformance-case.schema.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/conformance-case.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "candidate": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/transition.schema.json" - }, - "case_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "description": { - "minLength": 1, - "type": "string" - }, - "expected": { - "additionalProperties": false, - "properties": { - "accepted": { - "type": "boolean" - }, - "code": { - "minLength": 1, - "type": "string" - }, - "state_changed": { - "type": "boolean" - } - }, - "required": [ - "accepted", - "code", - "state_changed" - ], - "type": "object" - }, - "initial_genesis": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/root-genesis.schema.json" - }, - "postconditions": { - "items": { - "additionalProperties": false, - "properties": { - "equals": {}, - "path": { - "pattern": "^/", - "type": "string" - } - }, - "required": [ - "path", - "equals" - ], - "type": "object" - }, - "type": "array" - }, - "setup": { - "items": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/transition.schema.json" - }, - "type": "array" - } - }, - "required": [ - "case_id", - "description", - "initial_genesis", - "setup", - "candidate", - "expected", - "postconditions" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/constitution.schema.json b/src/aset_python_sqlite/schemas/constitution.schema.json deleted file mode 100644 index 80bdbae..0000000 --- a/src/aset_python_sqlite/schemas/constitution.schema.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/constitution.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "accepted_coordination_proofs": { - "items": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "accepted_patches": { - "items": { - "type": "object" - }, - "type": "array" - }, - "constitution_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "coordination_classes": { - "additionalProperties": { - "enum": [ - "MONOTONE_LOCAL", - "INVARIANT_CONFLUENT", - "COORDINATION_REQUIRED" - ] - }, - "type": "object" - }, - "identity_immunities": { - "items": { - "minLength": 1, - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "policy_version": { - "minimum": 1, - "type": "integer" - }, - "rules": { - "additionalProperties": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "type": "object" - } - }, - "required": [ - "constitution_id", - "policy_version", - "identity_immunities", - "coordination_classes", - "accepted_coordination_proofs", - "accepted_patches", - "rules" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/context-descriptor.schema.json b/src/aset_python_sqlite/schemas/context-descriptor.schema.json deleted file mode 100644 index cdab739..0000000 --- a/src/aset_python_sqlite/schemas/context-descriptor.schema.json +++ /dev/null @@ -1,152 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/context-descriptor.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "alias": { - "minLength": 1, - "type": "string" - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "context_kind": { - "enum": [ - "ROOT", - "FEDERATION", - "SUBJECT", - "ORGANIZATION", - "AI_AGENT" - ] - }, - "export_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "genesis_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "guarantee_status": { - "enum": [ - "CONFIRMED", - "SUSPENDED", - "TERMINATED" - ] - }, - "internal_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "last_confirmed_export_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "lifecycle": { - "enum": [ - "ACTIVE", - "WITHDRAWN", - "SUPERSEDED", - "TERMINATED" - ] - }, - "local_ordinal": { - "minimum": 0, - "type": "integer" - }, - "member_principal_id": { - "oneOf": [ - { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "parent_context_id": { - "oneOf": [ - { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "unconfirmed_commits": { - "additionalProperties": { - "additionalProperties": false, - "properties": { - "commit_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "commit_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "new_export_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "operation_class": { - "minLength": 1, - "type": "string" - }, - "parent_export_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "commit_id", - "parent_export_root", - "new_export_root", - "operation_class", - "commit_digest", - "signer_principal_id" - ], - "type": "object" - }, - "type": "object" - } - }, - "required": [ - "context_id", - "parent_context_id", - "context_kind", - "member_principal_id", - "genesis_digest", - "constitution_epoch", - "local_ordinal", - "lifecycle", - "guarantee_status", - "internal_state_root", - "export_root", - "last_confirmed_export_root", - "alias" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/context-redefinition-record.schema.json b/src/aset_python_sqlite/schemas/context-redefinition-record.schema.json deleted file mode 100644 index 1b50d7c..0000000 --- a/src/aset_python_sqlite/schemas/context-redefinition-record.schema.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/context-redefinition-record.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "affected_context_ids": { - "items": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "type": "array", - "uniqueItems": true - }, - "context_id": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "proposal": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-context-redefine.schema.json#/properties/proposal" - }, - "proposal_digest": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/digest" - }, - "redefinition_id": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "successor_map": { - "additionalProperties": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "type": "object" - }, - "target_context_id": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "transition_ref": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "withdrawal_refs": { - "items": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "type": "array", - "uniqueItems": true - } - }, - "required": [ - "redefinition_id", - "context_id", - "target_context_id", - "proposal", - "proposal_digest", - "affected_context_ids", - "successor_map", - "withdrawal_refs", - "transition_ref" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/correction-record.schema.json b/src/aset_python_sqlite/schemas/correction-record.schema.json deleted file mode 100644 index 4750591..0000000 --- a/src/aset_python_sqlite/schemas/correction-record.schema.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/correction-record.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "correction_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "corrector_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "reason_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "replacement_ref": { - "oneOf": [ - { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "target_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "target_type": { - "const": "VERIFICATION" - } - }, - "required": [ - "correction_id", - "context_id", - "target_type", - "target_ref", - "replacement_ref", - "reason_digest", - "corrector_principal_id" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/decision.schema.json b/src/aset_python_sqlite/schemas/decision.schema.json deleted file mode 100644 index 564ef04..0000000 --- a/src/aset_python_sqlite/schemas/decision.schema.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/decision.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "conditions_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "decision_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "decision_kind": { - "type": "string" - }, - "issuer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "related_ref": { - "oneOf": [ - { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "scope": { - "items": { - "maxLength": 120, - "minLength": 1, - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "scope_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "subject_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "decision_id", - "context_id", - "decision_kind", - "issuer_principal_id", - "subject_principal_id", - "scope", - "scope_digest", - "conditions_digest", - "related_ref", - "constitution_epoch" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/execution-intent.schema.json b/src/aset_python_sqlite/schemas/execution-intent.schema.json deleted file mode 100644 index ba5b71e..0000000 --- a/src/aset_python_sqlite/schemas/execution-intent.schema.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/execution-intent.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "candidate_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "execution_intent_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "permit_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "presenter_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "submission_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "execution_intent_id", - "context_id", - "permit_ref", - "presenter_principal_id", - "submission_id", - "candidate_digest" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/export-receipt.schema.json b/src/aset_python_sqlite/schemas/export-receipt.schema.json deleted file mode 100644 index fa74587..0000000 --- a/src/aset_python_sqlite/schemas/export-receipt.schema.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/export-receipt.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "claim_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "export_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "guarantee_status": { - "enum": [ - "CONFIRMED", - "SUSPENDED", - "TERMINATED" - ] - }, - "issuer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "outcome_ref": { - "oneOf": [ - { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "previous_export_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "source_context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "source_export_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "transition_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "export_id", - "context_id", - "source_context_id", - "source_export_root", - "claim_digest", - "outcome_ref", - "guarantee_status", - "issuer_principal_id", - "previous_export_root", - "transition_ref" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/import-observation.schema.json b/src/aset_python_sqlite/schemas/import-observation.schema.json deleted file mode 100644 index 4c36d7c..0000000 --- a/src/aset_python_sqlite/schemas/import-observation.schema.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/import-observation.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "claim_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "export_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "import_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "importer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "target_context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "import_id", - "context_id", - "target_context_id", - "export_ref", - "claim_digest", - "importer_principal_id" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/membership-withdrawal-record.schema.json b/src/aset_python_sqlite/schemas/membership-withdrawal-record.schema.json deleted file mode 100644 index c595488..0000000 --- a/src/aset_python_sqlite/schemas/membership-withdrawal-record.schema.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/membership-withdrawal-record.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "authorization_proof_digest": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/digest" - }, - "context_id": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "member_principal_id": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "mode": { - "enum": [ - "VOLUNTARY_EXIT", - "REDEFINITION" - ] - }, - "parent_context_id": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "proposal_digest": { - "oneOf": [ - { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/digest" - }, - { - "type": "null" - } - ] - }, - "reason_digest": { - "oneOf": [ - { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/digest" - }, - { - "type": "null" - } - ] - }, - "transition_ref": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "withdrawal_id": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "withdrawn_context_ids": { - "items": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "type": "array", - "uniqueItems": true - } - }, - "required": [ - "withdrawal_id", - "context_id", - "parent_context_id", - "mode", - "member_principal_id", - "reason_digest", - "proposal_digest", - "withdrawn_context_ids", - "authorization_proof_digest", - "transition_ref" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/observation.schema.json b/src/aset_python_sqlite/schemas/observation.schema.json deleted file mode 100644 index da3c337..0000000 --- a/src/aset_python_sqlite/schemas/observation.schema.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/observation.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "claim_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "claim_subject_context_id": { - "oneOf": [ - { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "evidence_refs": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "observation_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "observer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "permit_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "receipt_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "observation_id", - "context_id", - "permit_ref", - "receipt_ref", - "observer_principal_id", - "claim_digest", - "evidence_refs", - "claim_subject_context_id" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/outcome.schema.json b/src/aset_python_sqlite/schemas/outcome.schema.json deleted file mode 100644 index f99804b..0000000 --- a/src/aset_python_sqlite/schemas/outcome.schema.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/outcome.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "confirmer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "outcome_class": { - "enum": [ - "POSITIVE", - "NEGATIVE" - ] - }, - "outcome_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "permit_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "verification_refs": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - } - }, - "required": [ - "outcome_id", - "context_id", - "permit_ref", - "verification_refs", - "confirmer_principal_id", - "outcome_class" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-authority-transfer.schema.json b/src/aset_python_sqlite/schemas/payload-authority-transfer.schema.json deleted file mode 100644 index e8febee..0000000 --- a/src/aset_python_sqlite/schemas/payload-authority-transfer.schema.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-authority-transfer.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "authority_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "new_holder_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "outcome_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "authority_ref", - "outcome_ref", - "new_holder_principal_id" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-context-redefine.schema.json b/src/aset_python_sqlite/schemas/payload-context-redefine.schema.json deleted file mode 100644 index a38b8e9..0000000 --- a/src/aset_python_sqlite/schemas/payload-context-redefine.schema.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-context-redefine.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "proposal": { - "additionalProperties": false, - "properties": { - "parent_context_id": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "proposal_nonce": { - "minLength": 1, - "type": "string" - }, - "replacements": { - "items": { - "additionalProperties": false, - "properties": { - "context_genesis_nonce": { - "minLength": 1, - "type": "string" - }, - "depends_on_context_ids": { - "items": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "type": "array", - "uniqueItems": true - }, - "initial_authorities": { - "items": { - "additionalProperties": false, - "properties": { - "capability_kind": { - "minLength": 1, - "type": "string" - }, - "holder_principal_id": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "scope": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/scope" - } - }, - "required": [ - "capability_kind", - "holder_principal_id", - "scope" - ], - "type": "object" - }, - "type": "array", - "uniqueItems": true - }, - "old_context_id": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - } - }, - "required": [ - "old_context_id", - "context_genesis_nonce", - "initial_authorities", - "depends_on_context_ids" - ], - "type": "object" - }, - "minItems": 1, - "type": "array" - }, - "target_context_id": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - } - }, - "required": [ - "parent_context_id", - "target_context_id", - "proposal_nonce", - "replacements" - ], - "type": "object" - }, - "proposal_digest": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/digest" - }, - "withdrawal_authorizations": { - "items": { - "additionalProperties": false, - "properties": { - "context_id": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "member_principal_id": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "proof_digest": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/digest" - }, - "proposal_digest": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/digest" - } - }, - "required": [ - "context_id", - "member_principal_id", - "proposal_digest", - "proof_digest" - ], - "type": "object" - }, - "minItems": 1, - "type": "array" - } - }, - "required": [ - "proposal", - "proposal_digest", - "withdrawal_authorizations" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-context-terminate.schema.json b/src/aset_python_sqlite/schemas/payload-context-terminate.schema.json deleted file mode 100644 index b6ba4a0..0000000 --- a/src/aset_python_sqlite/schemas/payload-context-terminate.schema.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-context-terminate.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "child_context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "reason": { - "enum": [ - "TRUST_LINEAGE_LOST" - ] - }, - "verification_ref": { - "oneOf": [ - { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "child_context_id", - "reason", - "verification_ref" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-correction.schema.json b/src/aset_python_sqlite/schemas/payload-correction.schema.json deleted file mode 100644 index ffc6083..0000000 --- a/src/aset_python_sqlite/schemas/payload-correction.schema.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-correction.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "reason_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "replacement_ref": { - "oneOf": [ - { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "target_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "target_type": { - "const": "VERIFICATION" - } - }, - "required": [ - "target_type", - "target_ref", - "replacement_ref", - "reason_digest" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-decision.schema.json b/src/aset_python_sqlite/schemas/payload-decision.schema.json deleted file mode 100644 index 039c62b..0000000 --- a/src/aset_python_sqlite/schemas/payload-decision.schema.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-decision.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "conditions_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "decision_kind": { - "enum": [ - "READINESS_EXECUTE", - "READINESS_ACCEPT_RESPONSIBILITY", - "ISSUE_PERMIT", - "CONFIRM_OUTCOME", - "AMEND", - "SUSPEND_GUARANTEE", - "TERMINATE_CONTEXT", - "TRANSFER_AUTHORITY" - ] - }, - "related_ref": { - "oneOf": [ - { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "scope": { - "items": { - "maxLength": 120, - "minLength": 1, - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "subject_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "decision_kind", - "subject_principal_id", - "scope", - "conditions_digest", - "related_ref" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-export.schema.json b/src/aset_python_sqlite/schemas/payload-export.schema.json deleted file mode 100644 index 87ccb2a..0000000 --- a/src/aset_python_sqlite/schemas/payload-export.schema.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-export.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "claim_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "outcome_ref": { - "oneOf": [ - { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "source_export_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - } - }, - "required": [ - "source_export_root", - "claim_digest", - "outcome_ref" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-guarantee-suspend.schema.json b/src/aset_python_sqlite/schemas/payload-guarantee-suspend.schema.json deleted file mode 100644 index 26029f3..0000000 --- a/src/aset_python_sqlite/schemas/payload-guarantee-suspend.schema.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-guarantee-suspend.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "child_context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "reason_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - } - }, - "required": [ - "child_context_id", - "reason_digest" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-import.schema.json b/src/aset_python_sqlite/schemas/payload-import.schema.json deleted file mode 100644 index 9303870..0000000 --- a/src/aset_python_sqlite/schemas/payload-import.schema.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-import.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "export_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "local_permit_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "local_receipt_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "export_ref", - "local_permit_ref", - "local_receipt_ref" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-member-context-genesis.schema.json b/src/aset_python_sqlite/schemas/payload-member-context-genesis.schema.json deleted file mode 100644 index 34358e1..0000000 --- a/src/aset_python_sqlite/schemas/payload-member-context-genesis.schema.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-member-context-genesis.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "context_genesis_nonce": { - "minLength": 1, - "type": "string" - }, - "context_kind": { - "enum": [ - "FEDERATION", - "SUBJECT", - "ORGANIZATION", - "AI_AGENT" - ] - }, - "depends_on_context_ids": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "initial_authorities": { - "items": { - "additionalProperties": false, - "properties": { - "capability_kind": { - "minLength": 1, - "type": "string" - }, - "holder_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "scope": { - "items": { - "maxLength": 120, - "minLength": 1, - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - } - }, - "required": [ - "capability_kind", - "holder_principal_id", - "scope" - ], - "type": "object" - }, - "type": "array", - "uniqueItems": true - }, - "local_alias": { - "pattern": "^[A-Za-z0-9._-]+$", - "type": "string" - }, - "member_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "parent_context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "parent_context_id", - "member_principal_id", - "context_kind", - "context_genesis_nonce", - "local_alias", - "initial_authorities", - "depends_on_context_ids" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-membership-withdraw.schema.json b/src/aset_python_sqlite/schemas/payload-membership-withdraw.schema.json deleted file mode 100644 index 2aec58c..0000000 --- a/src/aset_python_sqlite/schemas/payload-membership-withdraw.schema.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-membership-withdraw.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "reason_digest": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/digest" - } - }, - "required": [ - "reason_digest" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-observation.schema.json b/src/aset_python_sqlite/schemas/payload-observation.schema.json deleted file mode 100644 index b91000b..0000000 --- a/src/aset_python_sqlite/schemas/payload-observation.schema.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-observation.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "claim_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "claim_subject_context_id": { - "oneOf": [ - { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "evidence_refs": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "permit_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "receipt_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "permit_ref", - "receipt_ref", - "claim_digest", - "evidence_refs", - "claim_subject_context_id" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-outcome.schema.json b/src/aset_python_sqlite/schemas/payload-outcome.schema.json deleted file mode 100644 index 197ad08..0000000 --- a/src/aset_python_sqlite/schemas/payload-outcome.schema.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-outcome.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "outcome_class": { - "enum": [ - "POSITIVE", - "NEGATIVE" - ] - }, - "permit_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "verification_refs": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - } - }, - "required": [ - "permit_ref", - "verification_refs", - "outcome_class" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-partition-local-transition.schema.json b/src/aset_python_sqlite/schemas/payload-partition-local-transition.schema.json deleted file mode 100644 index b168814..0000000 --- a/src/aset_python_sqlite/schemas/payload-partition-local-transition.schema.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-partition-local-transition.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "commit_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "coordination_proof_digest": { - "oneOf": [ - { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "operation_class": { - "minLength": 1, - "type": "string" - }, - "parent_export_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - } - }, - "required": [ - "operation_class", - "parent_export_root", - "commit_digest", - "coordination_proof_digest" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-permit-attenuate.schema.json b/src/aset_python_sqlite/schemas/payload-permit-attenuate.schema.json deleted file mode 100644 index e2a0001..0000000 --- a/src/aset_python_sqlite/schemas/payload-permit-attenuate.schema.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-permit-attenuate.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "caveats": { - "additionalProperties": { - "type": [ - "string", - "integer", - "boolean" - ] - }, - "type": "object" - }, - "delegate_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "max_attempts": { - "minimum": 1, - "type": "integer" - }, - "parent_permit_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "readiness_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "scope": { - "items": { - "maxLength": 120, - "minLength": 1, - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "validity_end_ordinal": { - "minimum": 1, - "type": "integer" - } - }, - "required": [ - "parent_permit_ref", - "delegate_principal_id", - "scope", - "max_attempts", - "validity_end_ordinal", - "caveats", - "readiness_ref" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-permit-issue.schema.json b/src/aset_python_sqlite/schemas/payload-permit-issue.schema.json deleted file mode 100644 index 923fb41..0000000 --- a/src/aset_python_sqlite/schemas/payload-permit-issue.schema.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-permit-issue.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "caveats": { - "additionalProperties": { - "type": [ - "string", - "integer", - "boolean" - ] - }, - "type": "object" - }, - "decision_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "delegate_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "max_attempts": { - "minimum": 1, - "type": "integer" - }, - "readiness_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "scope": { - "items": { - "maxLength": 120, - "minLength": 1, - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "stop_on_positive": { - "const": true - }, - "success_predicate_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "task_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "validity_end_ordinal": { - "minimum": 1, - "type": "integer" - } - }, - "required": [ - "decision_ref", - "readiness_ref", - "delegate_principal_id", - "task_digest", - "scope", - "success_predicate_digest", - "max_attempts", - "stop_on_positive", - "validity_end_ordinal", - "caveats" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-permit-use.schema.json b/src/aset_python_sqlite/schemas/payload-permit-use.schema.json deleted file mode 100644 index d0fe057..0000000 --- a/src/aset_python_sqlite/schemas/payload-permit-use.schema.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-permit-use.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "candidate_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "permit_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "submission_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "permit_ref", - "submission_id", - "candidate_digest" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-reconcile.schema.json b/src/aset_python_sqlite/schemas/payload-reconcile.schema.json deleted file mode 100644 index 1ba0f8c..0000000 --- a/src/aset_python_sqlite/schemas/payload-reconcile.schema.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-reconcile.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "child_context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "common_export_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "lineage": { - "items": { - "additionalProperties": false, - "properties": { - "commit_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "commit_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "new_export_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "operation_class": { - "minLength": 1, - "type": "string" - }, - "parent_export_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "commit_id", - "parent_export_root", - "new_export_root", - "operation_class", - "commit_digest", - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "type": "array" - } - }, - "required": [ - "child_context_id", - "common_export_root", - "lineage" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/payload-verification.schema.json b/src/aset_python_sqlite/schemas/payload-verification.schema.json deleted file mode 100644 index 3e71334..0000000 --- a/src/aset_python_sqlite/schemas/payload-verification.schema.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-verification.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "evidence_refs": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "observation_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "permit_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "policy_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "receipt_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "result_class": { - "enum": [ - "SUCCESS", - "FAILURE", - "TRUST_LINEAGE_LOST", - "UNDETERMINED" - ] - }, - "status": { - "enum": [ - "PASS", - "FAIL", - "UNKNOWN" - ] - } - }, - "required": [ - "permit_ref", - "receipt_ref", - "observation_ref", - "policy_digest", - "evidence_refs", - "status", - "result_class" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/permit-use-receipt.schema.json b/src/aset_python_sqlite/schemas/permit-use-receipt.schema.json deleted file mode 100644 index c3940ee..0000000 --- a/src/aset_python_sqlite/schemas/permit-use-receipt.schema.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/permit-use-receipt.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "attempt_index": { - "minimum": 1, - "type": "integer" - }, - "candidate_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "execution_intent_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "permit_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "presenter_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "receipt_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "submission_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "receipt_id", - "context_id", - "permit_ref", - "execution_intent_ref", - "presenter_principal_id", - "submission_id", - "candidate_digest", - "attempt_index" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/permit.schema.json b/src/aset_python_sqlite/schemas/permit.schema.json deleted file mode 100644 index b54eb7b..0000000 --- a/src/aset_python_sqlite/schemas/permit.schema.json +++ /dev/null @@ -1,154 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/permit.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "attempts_used": { - "minimum": 0, - "type": "integer" - }, - "caveats": { - "additionalProperties": { - "type": [ - "string", - "integer", - "boolean" - ] - }, - "type": "object" - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "decision_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "delegate_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "final_outcome_ref": { - "oneOf": [ - { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "issuer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "max_attempts": { - "minimum": 1, - "type": "integer" - }, - "parent_permit_ref": { - "oneOf": [ - { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "permit_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "readiness_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "scope": { - "items": { - "maxLength": 120, - "minLength": 1, - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "scope_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "status": { - "enum": [ - "ACTIVE", - "SATISFIED", - "EXHAUSTED", - "EXPIRED", - "REVOKED", - "TERMINATED_WITH_CONTEXT", - "UNRESOLVED", - "ATTENUATED" - ] - }, - "stop_on_positive": { - "const": true - }, - "success_predicate_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "task_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "validity_end_ordinal": { - "minimum": 1, - "type": "integer" - } - }, - "required": [ - "permit_id", - "context_id", - "issuer_principal_id", - "delegate_principal_id", - "decision_ref", - "readiness_ref", - "task_digest", - "scope", - "scope_digest", - "success_predicate_digest", - "max_attempts", - "attempts_used", - "stop_on_positive", - "validity_end_ordinal", - "caveats", - "status", - "final_outcome_ref", - "parent_permit_ref", - "constitution_epoch" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/reconciliation-receipt.schema.json b/src/aset_python_sqlite/schemas/reconciliation-receipt.schema.json deleted file mode 100644 index 9f551b9..0000000 --- a/src/aset_python_sqlite/schemas/reconciliation-receipt.schema.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/reconciliation-receipt.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "accepted_prefix_length": { - "minimum": 0, - "type": "integer" - }, - "child_context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "common_export_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "invalid_code": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "lineage_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "reconciliation_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "result": { - "enum": [ - "CONFIRMED", - "PARTIALLY_CONFIRMED", - "FORK_DETECTED", - "INSUFFICIENT_EVIDENCE" - ] - } - }, - "required": [ - "reconciliation_id", - "context_id", - "child_context_id", - "common_export_root", - "result", - "accepted_prefix_length", - "invalid_code", - "lineage_digest" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/root-genesis.schema.json b/src/aset_python_sqlite/schemas/root-genesis.schema.json deleted file mode 100644 index f94911a..0000000 --- a/src/aset_python_sqlite/schemas/root-genesis.schema.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/root-genesis.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "bootstrap_policy": { - "additionalProperties": false, - "properties": { - "allowed_context_kinds": { - "items": { - "enum": [ - "FEDERATION", - "SUBJECT", - "ORGANIZATION", - "AI_AGENT" - ] - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "allowed_initial_capabilities": { - "items": { - "minLength": 1, - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "max_admissions": { - "minimum": 1, - "type": "integer" - }, - "validator_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "validator_principal_id", - "max_admissions", - "allowed_context_kinds", - "allowed_initial_capabilities" - ], - "type": "object" - }, - "constitution": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/constitution.schema.json" - }, - "expected_constitution_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "expected_root_context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_root_genesis_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "expected_trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "external_anchor_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "root_context_nonce": { - "maxLength": 200, - "minLength": 1, - "type": "string" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "seed_semantics_id": { - "const": "aset-seed:0.1-rc11" - } - }, - "required": [ - "schema_version", - "seed_semantics_id", - "constitution", - "external_anchor_digest", - "root_context_nonce", - "bootstrap_policy", - "expected_constitution_digest", - "expected_root_genesis_digest", - "expected_root_context_id", - "expected_trust_space_id" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/transition.schema.json b/src/aset_python_sqlite/schemas/transition.schema.json deleted file mode 100644 index fbd24d3..0000000 --- a/src/aset_python_sqlite/schemas/transition.schema.json +++ /dev/null @@ -1,1590 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/transition.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "oneOf": [ - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "MEMBER_CONTEXT_GENESIS" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-member-context-genesis.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "DECISION" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-decision.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "PERMIT_ISSUE" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-permit-issue.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "PERMIT_ATTENUATE" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-permit-attenuate.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "PERMIT_USE" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-permit-use.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "OBSERVATION" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-observation.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "VERIFICATION" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-verification.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "OUTCOME" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-outcome.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "EXPORT" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-export.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "IMPORT" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-import.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "GUARANTEE_SUSPEND" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-guarantee-suspend.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "PARTITION_LOCAL_TRANSITION" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-partition-local-transition.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "RECONCILE" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-reconcile.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "MEMBERSHIP_WITHDRAW" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-membership-withdraw.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "CONTEXT_REDEFINE" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-context-redefine.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "CONTEXT_TERMINATE" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-context-terminate.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "CORRECTION" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-correction.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "authn": { - "additionalProperties": false, - "properties": { - "proof_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "signer_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "signer_principal_id", - "proof_digest" - ], - "type": "object" - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "constitution_epoch": { - "minimum": 0, - "type": "integer" - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "expected_local_ordinal": { - "minimum": 1, - "type": "integer" - }, - "kind": { - "const": "AUTHORITY_TRANSFER" - }, - "parent_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "payload": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/payload-authority-transfer.schema.json" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "schema_version", - "transition_id", - "trust_space_id", - "context_id", - "kind", - "parent_state_root", - "expected_local_ordinal", - "constitution_epoch", - "causal_parents", - "authn", - "payload" - ], - "type": "object" - } - ] -} diff --git a/src/aset_python_sqlite/schemas/trust-space-state.schema.json b/src/aset_python_sqlite/schemas/trust-space-state.schema.json deleted file mode 100644 index c828285..0000000 --- a/src/aset_python_sqlite/schemas/trust-space-state.schema.json +++ /dev/null @@ -1,386 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/trust-space-state.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "accepted_transition_count": { - "minimum": 0, - "type": "integer" - }, - "authorities": { - "additionalProperties": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/authority-binding.schema.json" - }, - "type": "object" - }, - "bootstrap": { - "additionalProperties": false, - "properties": { - "admissions_used": { - "minimum": 0, - "type": "integer" - }, - "open": { - "type": "boolean" - }, - "policy": { - "additionalProperties": false, - "properties": { - "allowed_context_kinds": { - "items": { - "enum": [ - "FEDERATION", - "SUBJECT", - "ORGANIZATION", - "AI_AGENT" - ] - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "allowed_initial_capabilities": { - "items": { - "minLength": 1, - "type": "string" - }, - "minItems": 1, - "type": "array", - "uniqueItems": true - }, - "max_admissions": { - "minimum": 1, - "type": "integer" - }, - "validator_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "validator_principal_id", - "max_admissions", - "allowed_context_kinds", - "allowed_initial_capabilities" - ], - "type": "object" - } - }, - "required": [ - "open", - "admissions_used", - "policy" - ], - "type": "object" - }, - "constitution": { - "additionalProperties": false, - "properties": { - "body": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/constitution.schema.json" - }, - "digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "epoch": { - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "epoch", - "digest", - "body" - ], - "type": "object" - }, - "context_aliases": { - "additionalProperties": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "object" - }, - "context_redefinitions": { - "additionalProperties": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/context-redefinition-record.schema.json" - }, - "type": "object" - }, - "contexts": { - "additionalProperties": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/context-descriptor.schema.json" - }, - "type": "object" - }, - "corrections": { - "additionalProperties": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/correction-record.schema.json" - }, - "type": "object" - }, - "current_state_root": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "decisions": { - "additionalProperties": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/decision.schema.json" - }, - "type": "object" - }, - "execution_intents": { - "additionalProperties": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/execution-intent.schema.json" - }, - "type": "object" - }, - "exports": { - "additionalProperties": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/export-receipt.schema.json" - }, - "type": "object" - }, - "external_anchor_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "imports": { - "additionalProperties": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/import-observation.schema.json" - }, - "type": "object" - }, - "membership_withdrawals": { - "additionalProperties": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/membership-withdrawal-record.schema.json" - }, - "type": "object" - }, - "normative_dependencies": { - "items": { - "additionalProperties": false, - "properties": { - "dependency_kind": { - "enum": [ - "NORMATIVE", - "EVIDENTIAL", - "INTERFACE", - "RESPONSIBILITY" - ] - }, - "source_context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "target_context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "source_context_id", - "target_context_id", - "dependency_kind" - ], - "type": "object" - }, - "type": "array" - }, - "observations": { - "additionalProperties": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/observation.schema.json" - }, - "type": "object" - }, - "outcomes": { - "additionalProperties": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/outcome.schema.json" - }, - "type": "object" - }, - "permit_use_receipts": { - "additionalProperties": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/permit-use-receipt.schema.json" - }, - "type": "object" - }, - "permits": { - "additionalProperties": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/permit.schema.json" - }, - "type": "object" - }, - "reconciliations": { - "additionalProperties": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/reconciliation-receipt.schema.json" - }, - "type": "object" - }, - "root_context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "root_genesis_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "schema_version": { - "const": "0.1-rc11" - }, - "seed_semantics_id": { - "const": "aset-seed:0.1-rc11" - }, - "submission_index": { - "additionalProperties": { - "additionalProperties": false, - "properties": { - "candidate_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "permit_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "receipt_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "permit_ref", - "candidate_digest", - "receipt_ref" - ], - "type": "object" - }, - "type": "object" - }, - "transition_records": { - "additionalProperties": { - "additionalProperties": false, - "properties": { - "accepted_index": { - "minimum": 1, - "type": "integer" - }, - "artifact_refs": { - "items": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "type": "array", - "uniqueItems": true - }, - "causal_basis_refs": { - "items": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/common.schema.json#/$defs/id" - }, - "type": "array", - "uniqueItems": true - }, - "causal_parents": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "kind": { - "type": "string" - }, - "transition_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "transition_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "transition_id", - "transition_digest", - "context_id", - "kind", - "causal_parents", - "causal_basis_refs", - "artifact_refs", - "accepted_index" - ], - "type": "object" - }, - "type": "object" - }, - "trust_space_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "verifications": { - "additionalProperties": { - "$ref": "https://spec.aset.example/seed/0.1-rc11/schemas/verification.schema.json" - }, - "type": "object" - } - }, - "required": [ - "schema_version", - "seed_semantics_id", - "trust_space_id", - "root_genesis_digest", - "external_anchor_digest", - "root_context_id", - "constitution", - "bootstrap", - "accepted_transition_count", - "current_state_root", - "contexts", - "context_aliases", - "authorities", - "decisions", - "permits", - "execution_intents", - "permit_use_receipts", - "submission_index", - "observations", - "verifications", - "outcomes", - "exports", - "imports", - "reconciliations", - "corrections", - "normative_dependencies", - "transition_records", - "membership_withdrawals", - "context_redefinitions" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/schemas/verification.schema.json b/src/aset_python_sqlite/schemas/verification.schema.json deleted file mode 100644 index dc7a207..0000000 --- a/src/aset_python_sqlite/schemas/verification.schema.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "$id": "https://spec.aset.example/seed/0.1-rc11/schemas/verification.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "properties": { - "context_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "evidence_refs": { - "items": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "type": "array", - "uniqueItems": true - }, - "observation_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "permit_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "policy_digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "receipt_ref": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "result_class": { - "enum": [ - "SUCCESS", - "FAILURE", - "TRUST_LINEAGE_LOST", - "UNDETERMINED" - ] - }, - "status": { - "enum": [ - "PASS", - "FAIL", - "UNKNOWN" - ] - }, - "verification_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - }, - "verifier_principal_id": { - "maxLength": 300, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+\\-]*$", - "type": "string" - } - }, - "required": [ - "verification_id", - "context_id", - "permit_ref", - "receipt_ref", - "observation_ref", - "verifier_principal_id", - "policy_digest", - "evidence_refs", - "status", - "result_class" - ], - "type": "object" -} diff --git a/src/aset_python_sqlite/seed_binding.py b/src/aset_python_sqlite/seed_binding.py new file mode 100644 index 0000000..18c8801 --- /dev/null +++ b/src/aset_python_sqlite/seed_binding.py @@ -0,0 +1,71 @@ +from __future__ import annotations + +from typing import Any + +CANON_ID = 'ASET-SEED-RESOLUTION-CANON-0.3-ALPHA1' +CANON_VERSION = '0.3.0-alpha.1' +CONFORMANCE_PROTOCOL = 'ASET-SEED-RESOLUTION-CONFORMANCE-V3' +CANON_PACKAGE_DIGEST = 'sha256:c5d48a418466ea7a60fccb7161adbd5ad568174bbc9a28fc03fd7e6e77955d31' +CONFORMANCE_PROFILE_SHA256 = 'sha256:4b84cbf665c5188a39f186bf4a8a51f82c6c77cac13efb4bc02a2cc6fad58f17' +ASET_RELEASE_TAG = 'seed-0.3.0-alpha.3' +ASET_RELEASE_COMMIT = '633c130187b2a2bb42f24cfd66662d475de385d2' +ASET_REPOSITORY = "https://github.com/attractor-set/ASET" +STANDARD_SERIES_ID = 'ASET-SEED-COMPATIBILITY-STANDARD' +STANDARD_PROFILE_ID = 'ASET-SEED-COMPATIBILITY-STANDARD-V1' +STANDARD_PROFILE_SHA256 = 'sha256:baf65fbdcf7d37ef1050b072063a36009c2bb9b393fd893c6f7c4fe1fb033fa2' +STANDARD_ID = 'ASET-SEED-COMPATIBILITY-STANDARD@seed-0.3.0-alpha.3' +CONFORMANCE_KIT_SHA256 = 'sha256:5ecf9b93377a062b8772b4b4b44b4d76a0997d8ba98e8711e717456abbe583db' +MANDATORY_CONFORMANCE_CASES = 25 +VERDICT_AUTHORITY = "external ASET conformance runner" +IMPLEMENTATION_PRECEDENCE = "NONE" +PROFILE_ID = "ASET-PYTHON-SQLITE-REFERENCE-V1" +STORE_SCHEMA_VERSION = "3" + + +def standard_document() -> dict[str, Any]: + return { + "conformance_kit_sha256": CONFORMANCE_KIT_SHA256, + "conformance_profile_sha256": CONFORMANCE_PROFILE_SHA256, + "mandatory_conformance_cases": MANDATORY_CONFORMANCE_CASES, + "release_version": "0.3.0-alpha.3", + "seed_semantic_version": CANON_VERSION, + "standard_id": STANDARD_ID, + "standard_profile_id": STANDARD_PROFILE_ID, + "standard_profile_sha256": STANDARD_PROFILE_SHA256, + "standard_series_id": STANDARD_SERIES_ID, + "verdict_authority": VERDICT_AUTHORITY, + } + + +def binding_document() -> dict[str, Any]: + return { + "canon_id": CANON_ID, + "canon_version": CANON_VERSION, + "conformance_protocol": CONFORMANCE_PROTOCOL, + "required_package_digest": CANON_PACKAGE_DIGEST, + "implementation_precedence": IMPLEMENTATION_PRECEDENCE, + "source": { + "repository": ASET_REPOSITORY, + "ref": ASET_RELEASE_COMMIT, + "tag": ASET_RELEASE_TAG, + }, + "standard": standard_document(), + } + + +def expected_store_metadata() -> dict[str, str]: + return { + "schema_version": STORE_SCHEMA_VERSION, + "profile_id": PROFILE_ID, + "compatibility_standard_id": STANDARD_ID, + "compatibility_standard_profile_id": STANDARD_PROFILE_ID, + "compatibility_standard_profile_sha256": STANDARD_PROFILE_SHA256, + "conformance_kit_sha256": CONFORMANCE_KIT_SHA256, + "conformance_profile_sha256": CONFORMANCE_PROFILE_SHA256, + "canon_id": CANON_ID, + "canon_version": CANON_VERSION, + "conformance_protocol": CONFORMANCE_PROTOCOL, + "canon_package_digest": CANON_PACKAGE_DIGEST, + "aset_release_tag": ASET_RELEASE_TAG, + "aset_release_commit": ASET_RELEASE_COMMIT, + } diff --git a/src/aset_python_sqlite/store.py b/src/aset_python_sqlite/store.py index 02e0c56..c9ad8ac 100644 --- a/src/aset_python_sqlite/store.py +++ b/src/aset_python_sqlite/store.py @@ -7,8 +7,7 @@ from collections.abc import Iterator from pathlib import Path -SCHEMA_VERSION = "1" -PROFILE_ID = "ASET-PYTHON-SQLITE-LEARNING-V1" +from .seed_binding import expected_store_metadata class StoreError(RuntimeError): @@ -29,8 +28,7 @@ def _require_private_posix_file(path: Path, label: str) -> None: raise StoreError(f"{label} must not be a symbolic link") if not stat.S_ISREG(status.st_mode): raise StoreError(f"{label} must be a regular file") - mode = stat.S_IMODE(status.st_mode) - if mode & 0o077: + if stat.S_IMODE(status.st_mode) & 0o077: raise StoreError(f"{label} must not be group/world accessible") @@ -84,39 +82,30 @@ def _initialize(self) -> None: key TEXT PRIMARY KEY, value TEXT NOT NULL ); - CREATE TABLE IF NOT EXISTS trust_spaces ( - trust_space_id TEXT PRIMARY KEY, - state_json TEXT NOT NULL, - state_root TEXT NOT NULL, - revision INTEGER NOT NULL, + CREATE TABLE IF NOT EXISTS seed_store ( + singleton INTEGER PRIMARY KEY CHECK (singleton = 1), + store_json TEXT NOT NULL, + store_digest TEXT NOT NULL, + revision INTEGER NOT NULL CHECK (revision >= 0), created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); - CREATE TABLE IF NOT EXISTS transition_attempts ( + CREATE TABLE IF NOT EXISTS operation_attempts ( sequence INTEGER PRIMARY KEY AUTOINCREMENT, - trust_space_id TEXT NOT NULL, - transition_id TEXT NOT NULL, - transition_json TEXT NOT NULL, + operation_json TEXT NOT NULL, + environment_json TEXT NOT NULL, result_json TEXT NOT NULL, - before_state_root TEXT NOT NULL, - after_state_root TEXT NOT NULL, - accepted INTEGER NOT NULL CHECK (accepted IN (0, 1)), + before_store_digest TEXT NOT NULL, + after_store_digest TEXT NOT NULL, state_changed INTEGER NOT NULL CHECK (state_changed IN (0, 1)), code TEXT NOT NULL, - proof_profile TEXT NOT NULL, previous_entry_hash TEXT NOT NULL, entry_hash TEXT NOT NULL UNIQUE, - created_at TEXT NOT NULL, - FOREIGN KEY (trust_space_id) REFERENCES trust_spaces(trust_space_id) + created_at TEXT NOT NULL ); - CREATE INDEX IF NOT EXISTS idx_transition_attempts_space_sequence - ON transition_attempts(trust_space_id, sequence); - CREATE INDEX IF NOT EXISTS idx_transition_attempts_transition - ON transition_attempts(trust_space_id, transition_id); """ ) - expected = {"schema_version": SCHEMA_VERSION, "profile_id": PROFILE_ID} - for key, value in expected.items(): + for key, value in expected_store_metadata().items(): row = connection.execute( "SELECT value FROM metadata WHERE key=?", (key,), @@ -127,7 +116,17 @@ def _initialize(self) -> None: (key, value), ) elif row["value"] != value: - raise StoreError(f"database metadata mismatch for {key}") + raise StoreError(f"database seed binding mismatch for {key}") + finally: + connection.close() + + def metadata(self) -> dict[str, str]: + connection = self.connect() + try: + return { + row["key"]: row["value"] + for row in connection.execute("SELECT key, value FROM metadata ORDER BY key") + } finally: connection.close() @@ -147,6 +146,14 @@ def backup(self, destination: Path) -> None: integrity = target.execute("PRAGMA integrity_check").fetchone()[0] if integrity != "ok": raise StoreError(f"backup integrity check failed: {integrity}") + expected = expected_store_metadata() + actual = { + row[0]: row[1] + for row in target.execute("SELECT key, value FROM metadata ORDER BY key") + } + for key, value in expected.items(): + if actual.get(key) != value: + raise StoreError(f"backup seed binding mismatch for {key}") succeeded = True finally: target.close() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/POS-001.json b/tests/fixtures/POS-001.json deleted file mode 100644 index c4de555..0000000 --- a/tests/fixtures/POS-001.json +++ /dev/null @@ -1,170 +0,0 @@ -{ - "candidate": { - "authn": { - "proof_digest": "sha256:e3cff0abcfb4ea1d04a2ba1717cd1c747c4c39bff191129dc82c50aafbdb7358", - "signer_principal_id": "principal:bootstrap" - }, - "causal_parents": [], - "constitution_epoch": 0, - "context_id": "ctx:6c94166c411b9579e2d06af3157708d3bdc1cc4d4179bad7cfb4c79dc878b441", - "expected_local_ordinal": 1, - "kind": "MEMBER_CONTEXT_GENESIS", - "parent_state_root": "sha256:fc65383cffe7f067c6fd1ea829653b86c7449fb279da12d12151670272ad9bb9", - "payload": { - "context_genesis_nonce": "f-nonce", - "context_kind": "FEDERATION", - "depends_on_context_ids": [], - "initial_authorities": [ - { - "capability_kind": "CREATE_MEMBER_CONTEXT", - "holder_principal_id": "principal:responsible", - "scope": [ - "*" - ] - }, - { - "capability_kind": "ISSUE_PERMIT", - "holder_principal_id": "principal:responsible", - "scope": [ - "*" - ] - }, - { - "capability_kind": "VERIFY", - "holder_principal_id": "principal:validator", - "scope": [ - "*" - ] - }, - { - "capability_kind": "CONFIRM_OUTCOME", - "holder_principal_id": "principal:responsible", - "scope": [ - "*" - ] - }, - { - "capability_kind": "EXPORT", - "holder_principal_id": "principal:responsible", - "scope": [ - "*" - ] - }, - { - "capability_kind": "IMPORT", - "holder_principal_id": "principal:responsible", - "scope": [ - "*" - ] - }, - { - "capability_kind": "SUSPEND_GUARANTEE", - "holder_principal_id": "principal:responsible", - "scope": [ - "*" - ] - }, - { - "capability_kind": "RECONCILE", - "holder_principal_id": "principal:responsible", - "scope": [ - "*" - ] - }, - { - "capability_kind": "REDEFINE_CONTEXT", - "holder_principal_id": "principal:responsible", - "scope": [ - "*" - ] - }, - { - "capability_kind": "TERMINATE_CONTEXT", - "holder_principal_id": "principal:responsible", - "scope": [ - "*" - ] - }, - { - "capability_kind": "TRANSFER_AUTHORITY", - "holder_principal_id": "principal:responsible", - "scope": [ - "*" - ] - } - ], - "local_alias": "f", - "member_principal_id": "principal:responsible", - "parent_context_id": "ctx:6c94166c411b9579e2d06af3157708d3bdc1cc4d4179bad7cfb4c79dc878b441" - }, - "schema_version": "0.1-rc11", - "transition_id": "tx:241bd82aac02628039ad516bec3943b883df83c631739f48c2c9f6b54d7b74d0", - "trust_space_id": "ts:1609bab738b98092e3d8032e7e55fc2e52a56700bce2abb266c83fbdde1e5878" - }, - "case_id": "POS-001", - "description": "First federation is admitted by the precommitted bootstrap validator.", - "expected": { - "accepted": true, - "code": "ACCEPTED", - "state_changed": true - }, - "initial_genesis": { - "bootstrap_policy": { - "allowed_context_kinds": [ - "FEDERATION" - ], - "allowed_initial_capabilities": [ - "CREATE_MEMBER_CONTEXT", - "ISSUE_PERMIT", - "VERIFY", - "CONFIRM_OUTCOME", - "EXPORT", - "IMPORT", - "SUSPEND_GUARANTEE", - "RECONCILE", - "REDEFINE_CONTEXT", - "TERMINATE_CONTEXT", - "TRANSFER_AUTHORITY" - ], - "max_admissions": 1, - "validator_principal_id": "principal:bootstrap" - }, - "constitution": { - "accepted_coordination_proofs": [ - "sha256:a4ce6f8a8a3ea88cbc11a2b96ef662e83ca21e5c84109644b962e4236b2ef478" - ], - "accepted_patches": [], - "constitution_id": "constitution:root", - "coordination_classes": { - "APPEND_EVIDENCE": "MONOTONE_LOCAL", - "APPEND_OBSERVATION": "MONOTONE_LOCAL", - "AUTHORITY_CHANGE": "COORDINATION_REQUIRED", - "CONTEXT_REDEFINE": "COORDINATION_REQUIRED", - "SAFE_COUNTER": "INVARIANT_CONFLUENT" - }, - "identity_immunities": [ - "/seed_semantics_id", - "/trust_space_id", - "/root_context_id", - "/contexts/*/genesis_digest", - "/contexts/*/parent_context_id" - ], - "policy_version": 1, - "rules": { - "rule:bootstrap": "sha256:5219664ad4a9acbf0b7a143ca79ce141f5e87fdb8752efb740163f89c2b9a2ca", - "rule:permit": "sha256:4143c4f029ca389a681115121da329498ac2278ae1a3663976a23fcebe486ef4", - "rule:verification": "sha256:cc75134587dcd7fa8aa4d1e55a355e8643d61136a3d58fee8d4c57b23a9440e3" - } - }, - "expected_constitution_digest": "sha256:f43d179174218db9e891925e96c22f349993f8ace8c169e1f7d3404dc42e9674", - "expected_root_context_id": "ctx:6c94166c411b9579e2d06af3157708d3bdc1cc4d4179bad7cfb4c79dc878b441", - "expected_root_genesis_digest": "sha256:5584c7d80898c97627e6100b1f6bc7bcf23c90f373d722b1fc46f5fe03bad79a", - "expected_trust_space_id": "ts:1609bab738b98092e3d8032e7e55fc2e52a56700bce2abb266c83fbdde1e5878", - "external_anchor_digest": "sha256:57da8a3caad7a8de81074ec503dc647ef56e4510d1f2deddb0e2ffda8735392d", - "root_context_nonce": "root-nonce-001", - "schema_version": "0.1-rc11", - "seed_semantics_id": "aset-seed:0.1-rc11" - }, - "postconditions": [], - "setup": [] -} diff --git a/tests/helpers.py b/tests/helpers.py new file mode 100644 index 0000000..3560e0f --- /dev/null +++ b/tests/helpers.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from typing import Any + +from aset_python_sqlite.kernel import digest_value + + +def seed_objects() -> dict[str, Any]: + binding = { + "context_id": "ctx.local", + "policy_epoch": 1, + "question_digest": "sha256:" + "b" * 64, + "scope": ["effect:publish"], + "state_root": "sha256:" + "a" * 64, + } + binding["binding_digest"] = digest_value(binding) + + root_authority = { + "authority_id": "authority.root", + "binding_digest": binding["binding_digest"], + "context_id": binding["context_id"], + "policy_epoch": binding["policy_epoch"], + } + root_authority["authority_binding_digest"] = digest_value(root_authority) + + request = { + "binding": binding, + "initial_authority_binding_digest": root_authority["authority_binding_digest"], + "previous_terminal_record_digest": None, + "resolution_id": "res.1", + } + request["request_digest"] = digest_value(request) + + record = { + "authority_evidence_digests": ["sha256:" + "c" * 64], + "authority_id": "authority.root", + "binding_digest": binding["binding_digest"], + "request_digest": request["request_digest"], + "resolution": "ALLOW", + "resolution_id": "res.1", + } + record["record_digest"] = digest_value(record) + + return { + "binding": binding, + "authority": root_authority, + "request": request, + "record": record, + } + + +def initial_store() -> dict[str, list[dict[str, Any]]]: + objects = seed_objects() + return { + "requests": [], + "records": [], + "authority_bindings": [objects["authority"]], + } + + +def register_operation(request: dict[str, Any] | None = None) -> dict[str, Any]: + return { + "kind": "REGISTER_REQUEST", + "payload": {"request": request or seed_objects()["request"]}, + } + + +def submit_operation(record: dict[str, Any] | None = None) -> dict[str, Any]: + return { + "kind": "SUBMIT_RESOLUTION", + "payload": {"record": record or seed_objects()["record"]}, + } + + +def evaluate_operation(resolution_id: str = "res.1") -> dict[str, Any]: + return { + "kind": "EVALUATE_RESOLUTION", + "payload": {"resolution_id": resolution_id}, + } diff --git a/tests/test_adapter.py b/tests/test_adapter.py index 0353267..87cbe3b 100644 --- a/tests/test_adapter.py +++ b/tests/test_adapter.py @@ -1,23 +1,64 @@ from __future__ import annotations + import json import subprocess import sys from pathlib import Path -ROOT=Path(__file__).resolve().parents[1] -CASE=json.loads((ROOT/'tests/fixtures/POS-001.json').read_text(encoding='utf-8')) +from aset_python_sqlite.seed_binding import ( + CONFORMANCE_KIT_SHA256, + CONFORMANCE_PROTOCOL, + PROFILE_ID, + STANDARD_ID, +) +from tests.helpers import initial_store, register_operation + +ROOT = Path(__file__).resolve().parents[1] + + +def invoke(request: dict) -> dict: + completed = subprocess.run( + [sys.executable, "tools/adapter_entry.py"], + cwd=ROOT, + input=json.dumps(request), + text=True, + capture_output=True, + check=False, + ) + assert completed.returncode == 0, completed.stderr + return json.loads(completed.stdout) + -def invoke(request): - p=subprocess.run([sys.executable,'tools/adapter_entry.py'],cwd=ROOT,input=json.dumps(request),text=True,capture_output=True,check=False) - assert p.returncode==0,p.stderr - return json.loads(p.stdout) +def test_describe_is_non_normative_and_seed_bound() -> None: + response = invoke({"protocol": CONFORMANCE_PROTOCOL, "operation": "describe"}) + implementation = response["implementation"] + assert implementation["normative"] is False + assert implementation["profile_id"] == PROFILE_ID + assert implementation["seed_release_tag"] == "seed-0.3.0-alpha.3" + assert implementation["seed_release_commit"] == "633c130187b2a2bb42f24cfd66662d475de385d2" + assert implementation["compatibility_standard_id"] == STANDARD_ID + assert implementation["conformance_kit_sha256"] == CONFORMANCE_KIT_SHA256 -def test_describe_is_non_normative(): - r=invoke({'protocol':'ASET-IMPLEMENTATION-CONFORMANCE-V1','operation':'describe'}) - assert r['implementation']['normative'] is False - assert r['implementation']['profile_id']=='ASET-PYTHON-SQLITE-LEARNING-V1' -def test_execute_case_returns_observation_not_verdict(): - r=invoke({'protocol':'ASET-IMPLEMENTATION-CONFORMANCE-V1','operation':'execute_case','case':CASE}) - assert r['actual']==CASE['expected'] - assert 'pass' not in r and 'verdict' not in r +def test_adapter_returns_observation_not_verdict() -> None: + case = { + "case_id": "LOCAL-POS-001", + "initial_store": initial_store(), + "setup": [], + "candidate": register_operation(), + "expected": { + "accepted": True, + "code": "REQUEST_REGISTERED", + "state_changed": True, + "resolution": "UNKNOWN", + "effect_permitted": False, + "reason": "TERMINAL_RECORD_ABSENT", + }, + "postconditions": [], + } + response = invoke( + {"protocol": CONFORMANCE_PROTOCOL, "operation": "execute_case", "case": case} + ) + assert response["actual"] == case["expected"] + assert response["final_store"]["requests"][0]["resolution_id"] == "res.1" + assert "pass" not in response and "verdict" not in response diff --git a/tests/test_cli_security.py b/tests/test_cli_security.py deleted file mode 100644 index ca17503..0000000 --- a/tests/test_cli_security.py +++ /dev/null @@ -1,50 +0,0 @@ -from __future__ import annotations - -import json -import os -from pathlib import Path - -import pytest - -from aset_python_sqlite.cli import _load_verifier -from aset_python_sqlite.jsonio import StrictJsonError - - -def test_proof_secret_file_must_be_private_on_posix(tmp_path: Path): - if os.name != "posix": - pytest.skip("POSIX permission profile only") - path = tmp_path / "secrets.json" - path.write_text( - json.dumps( - { - "document_type": "aset-seed-hmac-secret-map", - "profile": "HMAC_SHA256_V1", - "secrets": {}, - } - ), - encoding="utf-8", - ) - os.chmod(path, 0o644) - with pytest.raises(StrictJsonError): - _load_verifier(path) - os.chmod(path, 0o600) - verifier = _load_verifier(path) - assert verifier.profile_id == "HMAC_SHA256_V1" - - -def test_proof_secret_values_must_decode_to_32_bytes(tmp_path: Path): - path = tmp_path / "secrets.json" - path.write_text( - json.dumps( - { - "document_type": "aset-seed-hmac-secret-map", - "profile": "HMAC_SHA256_V1", - "secrets": {"principal:test": "c2hvcnQ="}, - } - ), - encoding="utf-8", - ) - if os.name == "posix": - os.chmod(path, 0o600) - with pytest.raises(ValueError): - _load_verifier(path) diff --git a/tests/test_jsonio.py b/tests/test_jsonio.py new file mode 100644 index 0000000..7a3b88b --- /dev/null +++ b/tests/test_jsonio.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +import pytest + +from aset_python_sqlite.jsonio import StrictJsonError, loads_strict + + +def test_strict_json_rejects_duplicate_members() -> None: + with pytest.raises(StrictJsonError): + loads_strict('{"x":1,"x":2}') diff --git a/tests/test_kernel.py b/tests/test_kernel.py new file mode 100644 index 0000000..f2e636c --- /dev/null +++ b/tests/test_kernel.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +import copy + +from aset_python_sqlite import kernel +from tests.helpers import evaluate_operation, initial_store, register_operation, seed_objects, submit_operation + + +def test_register_submit_and_evaluate_match_seed_resolution_semantics() -> None: + store = initial_store() + registered, store = kernel.execute_operation(store, register_operation()) + assert registered == { + "accepted": True, + "code": "REQUEST_REGISTERED", + "state_changed": True, + "resolution": "UNKNOWN", + "effect_permitted": False, + "reason": "TERMINAL_RECORD_ABSENT", + } + + submitted, store = kernel.execute_operation(store, submit_operation()) + assert submitted == { + "accepted": True, + "code": "RESOLUTION_RECORDED", + "state_changed": True, + "resolution": "ALLOW", + "effect_permitted": True, + "reason": "UNIQUE_VALID_TERMINAL_RECORD", + } + + before = copy.deepcopy(store) + evaluated, after = kernel.execute_operation(store, evaluate_operation()) + assert evaluated["accepted"] is True + assert evaluated["code"] == "EVALUATED" + assert evaluated["resolution"] == "ALLOW" + assert evaluated["effect_permitted"] is True + assert evaluated["state_changed"] is False + assert after == before + + +def test_terminal_record_is_immutable_and_exact_replay_is_idempotent() -> None: + _, store = kernel.execute_operation(initial_store(), register_operation()) + first, store = kernel.execute_operation(store, submit_operation()) + replay, replay_store = kernel.execute_operation(store, submit_operation()) + assert first["state_changed"] is True + assert replay["accepted"] is True + assert replay["code"] == "IDEMPOTENT_REPLAY" + assert replay["state_changed"] is False + assert replay_store == store + + conflicting = copy.deepcopy(seed_objects()["record"]) + conflicting["resolution"] = "BLOCK" + conflicting.pop("record_digest") + conflicting["record_digest"] = kernel.digest_value(conflicting) + rejected, rejected_store = kernel.execute_operation(store, submit_operation(conflicting)) + assert rejected["accepted"] is False + assert rejected["code"] == "TERMINAL_IMMUTABLE" + assert rejected_store == store + + +def test_previous_terminal_commitment_requires_recognition() -> None: + objects = seed_objects() + request = copy.deepcopy(objects["request"]) + request["resolution_id"] = "res.2" + request["previous_terminal_record_digest"] = "sha256:" + "d" * 64 + request.pop("request_digest") + request["request_digest"] = kernel.digest_value(request) + + rejected, _ = kernel.execute_operation(initial_store(), register_operation(request)) + assert rejected["code"] == "PREVIOUS_TERMINAL_COMMITMENT_UNRECOGNIZED" + + accepted, store = kernel.execute_operation( + initial_store(), + register_operation(request), + [request["previous_terminal_record_digest"]], + ) + assert accepted["code"] == "REQUEST_REGISTERED" + assert store["requests"][0]["resolution_id"] == "res.2" + + +def test_allow_is_recognized_but_no_external_effect_is_executed() -> None: + _, store = kernel.execute_operation(initial_store(), register_operation()) + result, store = kernel.execute_operation(store, submit_operation()) + assert result["resolution"] == "ALLOW" + assert result["effect_permitted"] is True + assert set(store) == {"requests", "records", "authority_bindings"} diff --git a/tests/test_prefreeze_hardening.py b/tests/test_prefreeze_hardening.py deleted file mode 100644 index e5b2d2e..0000000 --- a/tests/test_prefreeze_hardening.py +++ /dev/null @@ -1,290 +0,0 @@ -from __future__ import annotations - -import copy -import json -import os -import sqlite3 -from pathlib import Path - -import pytest - -from aset_python_sqlite.proofs import ( - HmacSha256ProofVerifier, - RejectAllProofVerifier, - sign_transition_hmac, -) -from aset_python_sqlite.runtime import MAX_TRANSITION_BYTES, DurableSeedRuntime -from aset_python_sqlite.store import StoreError - -ROOT = Path(__file__).resolve().parents[1] -CASE = ROOT / "tests/fixtures/POS-001.json" -SECRET = b"rc12-prefreeze-hardening-secret!!" - - -def case() -> dict: - return json.loads(CASE.read_text(encoding="utf-8")) - - -def verifier() -> HmacSha256ProofVerifier: - return HmacSha256ProofVerifier({"principal:bootstrap": SECRET}) - - -def attempt_count(database: Path) -> int: - connection = sqlite3.connect(database) - try: - return connection.execute("SELECT COUNT(*) FROM transition_attempts").fetchone()[0] - finally: - connection.close() - - -def test_health_fails_on_schema_invalid_persisted_state(tmp_path: Path): - data = case() - database = tmp_path / "seed.db" - runtime = DurableSeedRuntime(database, proof_verifier=verifier()) - state = runtime.initialize(data["initial_genesis"]) - - connection = sqlite3.connect(database) - try: - persisted = json.loads( - connection.execute( - "SELECT state_json FROM trust_spaces WHERE trust_space_id=?", - (state["trust_space_id"],), - ).fetchone()[0] - ) - persisted.pop("contexts") - connection.execute( - "UPDATE trust_spaces SET state_json=? WHERE trust_space_id=?", - (json.dumps(persisted, sort_keys=True), state["trust_space_id"]), - ) - connection.commit() - finally: - connection.close() - - status = runtime.health() - assert status.database_integrity == "ok" - assert status.state_validation == "FAIL" - - -def test_health_fails_on_stored_root_mismatch(tmp_path: Path): - data = case() - database = tmp_path / "seed.db" - runtime = DurableSeedRuntime(database, proof_verifier=verifier()) - runtime.initialize(data["initial_genesis"]) - - connection = sqlite3.connect(database) - try: - connection.execute( - "UPDATE trust_spaces SET state_root=?", - ("sha256:" + "f" * 64,), - ) - connection.commit() - finally: - connection.close() - - assert runtime.health().state_validation == "FAIL" - - -def test_oversized_transition_is_rejected_and_audited_by_digest(tmp_path: Path): - data = case() - database = tmp_path / "seed.db" - runtime = DurableSeedRuntime(database, proof_verifier=RejectAllProofVerifier()) - state = runtime.initialize(data["initial_genesis"]) - transition = copy.deepcopy(data["candidate"]) - transition["oversized_untrusted_input"] = "x" * (MAX_TRANSITION_BYTES + 1) - - result = runtime.apply(state["trust_space_id"], transition) - - assert result["code"] == "TRANSITION_TOO_LARGE" - assert attempt_count(database) == 1 - connection = sqlite3.connect(database) - try: - recorded = json.loads( - connection.execute("SELECT transition_json FROM transition_attempts").fetchone()[0] - ) - finally: - connection.close() - assert recorded["document_type"] == "aset-seed-oversized-transition-reference" - assert recorded["size_bytes"] > MAX_TRANSITION_BYTES - assert recorded["sha256"].startswith("sha256:") - assert runtime.verify_audit_chain(state["trust_space_id"]) - - -def test_non_json_embedded_input_returns_stable_boundary_rejection(tmp_path: Path): - data = case() - database = tmp_path / "seed.db" - runtime = DurableSeedRuntime(database, proof_verifier=RejectAllProofVerifier()) - state = runtime.initialize(data["initial_genesis"]) - - result = runtime.apply(state["trust_space_id"], {"not_json": {"a", "b"}}) - - assert result == { - "accepted": False, - "code": "INPUT_NOT_JSON_VALUE", - "state_changed": False, - "artifacts": [], - } - assert attempt_count(database) == 0 - - -def test_unknown_trust_space_returns_stable_boundary_rejection(tmp_path: Path): - runtime = DurableSeedRuntime(tmp_path / "seed.db") - result = runtime.apply("ts:" + "0" * 64, {}) - assert result == { - "accepted": False, - "code": "TRUST_SPACE_UNKNOWN", - "state_changed": False, - "artifacts": [], - } - - -def test_invalid_trust_space_identifier_returns_stable_boundary_rejection( - tmp_path: Path, -): - runtime = DurableSeedRuntime(tmp_path / "seed.db") - result = runtime.apply({"not": "an identifier"}, {}) - assert result == { - "accepted": False, - "code": "TRUST_SPACE_ID_INVALID", - "state_changed": False, - "artifacts": [], - } - - -class RaisingVerifier: - profile_id = "TEST_RAISING_VERIFIER" - - def verify(self, transition: dict) -> bool: - raise RuntimeError("secret diagnostic must not escape") - - -def test_proof_verifier_exception_is_stable_and_audited(tmp_path: Path): - data = case() - database = tmp_path / "seed.db" - runtime = DurableSeedRuntime(database, proof_verifier=RaisingVerifier()) - state = runtime.initialize(data["initial_genesis"]) - - result = runtime.apply(state["trust_space_id"], data["candidate"]) - - assert result == { - "accepted": False, - "code": "PROOF_VERIFIER_ERROR", - "state_changed": False, - "artifacts": [], - } - assert attempt_count(database) == 1 - assert runtime.verify_audit_chain(state["trust_space_id"]) - - -def test_hmac_proof_is_bound_to_exact_transition_content(tmp_path: Path): - data = case() - database = tmp_path / "seed.db" - runtime = DurableSeedRuntime(database, proof_verifier=verifier()) - state = runtime.initialize(data["initial_genesis"]) - transition = sign_transition_hmac(data["candidate"], SECRET) - transition["payload"]["local_alias"] = "modified-after-proof" - transition["transition_id"] = data["candidate"]["transition_id"] - - result = runtime.apply(state["trust_space_id"], transition) - - assert result["code"] == "PROOF_REJECTED" - assert result["state_changed"] is False - assert runtime.get_state(state["trust_space_id"])["current_state_root"] == state[ - "current_state_root" - ] - - -def test_backup_rejects_logically_invalid_state(tmp_path: Path): - data = case() - database = tmp_path / "seed.db" - runtime = DurableSeedRuntime(database, proof_verifier=verifier()) - runtime.initialize(data["initial_genesis"]) - - connection = sqlite3.connect(database) - try: - persisted = json.loads( - connection.execute("SELECT state_json FROM trust_spaces").fetchone()[0] - ) - persisted.pop("contexts") - connection.execute( - "UPDATE trust_spaces SET state_json=?", - (json.dumps(persisted, sort_keys=True),), - ) - connection.commit() - finally: - connection.close() - - destination = tmp_path / "invalid-backup.db" - with pytest.raises(StoreError, match="health validation"): - runtime.backup(destination) - assert not destination.exists() - - -def test_corrupted_stored_state_is_not_returned_or_executed(tmp_path: Path): - data = case() - database = tmp_path / "seed.db" - runtime = DurableSeedRuntime(database, proof_verifier=verifier()) - state = runtime.initialize(data["initial_genesis"]) - - connection = sqlite3.connect(database) - try: - persisted = json.loads( - connection.execute("SELECT state_json FROM trust_spaces").fetchone()[0] - ) - persisted.pop("contexts") - connection.execute( - "UPDATE trust_spaces SET state_json=?", - (json.dumps(persisted, sort_keys=True),), - ) - connection.commit() - finally: - connection.close() - - with pytest.raises(StoreError, match="stored state validation failed"): - runtime.get_state(state["trust_space_id"]) - - result = runtime.apply(state["trust_space_id"], data["candidate"]) - assert result["code"] == "STORED_STATE_INVALID" - assert attempt_count(database) == 1 - assert runtime.verify_audit_chain(state["trust_space_id"]) - - -def test_idempotent_initialize_rejects_corrupted_stored_state(tmp_path: Path): - data = case() - database = tmp_path / "seed.db" - runtime = DurableSeedRuntime(database, proof_verifier=verifier()) - runtime.initialize(data["initial_genesis"]) - connection = sqlite3.connect(database) - try: - connection.execute( - "UPDATE trust_spaces SET state_json='{}'", - ) - connection.commit() - finally: - connection.close() - with pytest.raises(StoreError, match="stored state validation failed"): - runtime.initialize(data["initial_genesis"]) - - -def test_existing_database_symlink_is_rejected(tmp_path: Path): - if os.name != "posix": - pytest.skip("POSIX symlink profile only") - target = tmp_path / "target.db" - runtime = DurableSeedRuntime(target) - runtime.initialize(case()["initial_genesis"]) - link = tmp_path / "link.db" - link.symlink_to(target) - with pytest.raises(StoreError, match="symbolic link"): - DurableSeedRuntime(link) - - -def test_runtime_dependency_is_exactly_pinned(): - pyproject = (ROOT / "pyproject.toml").read_text(encoding="utf-8") - assert 'dependencies = ["jsonschema==4.26.0"]' in pyproject - - -def test_runtime_guarantees_are_profile_local(): - profile = json.loads((ROOT / "profile/PROFILE.json").read_text(encoding="utf-8")) - assert profile["normative"] is False - assert profile["production_ready"] is False - assert profile["storage"]["engine"] == "SQLite" - assert profile["semantic_authority"] == "EXTERNAL_ASET_CANON" diff --git a/tests/test_release_assurance.py b/tests/test_release_assurance.py index 252ede1..c540281 100644 --- a/tests/test_release_assurance.py +++ b/tests/test_release_assurance.py @@ -10,7 +10,7 @@ ROOT = Path(__file__).resolve().parents[1] -def test_sqlite_connection_uses_wal_and_full_synchronous(tmp_path: Path) -> None: +def test_sqlite_connection_uses_wal_full_and_begin_immediate_profile(tmp_path: Path) -> None: store = SqliteStore(tmp_path / "profile.db") connection = store.connect() try: @@ -20,6 +20,8 @@ def test_sqlite_connection_uses_wal_and_full_synchronous(tmp_path: Path) -> None connection.close() assert str(journal_mode).lower() == "wal" assert synchronous == 2 + source = (ROOT / "src/aset_python_sqlite/store.py").read_text(encoding="utf-8") + assert 'connection.execute("BEGIN IMMEDIATE")' in source def test_origin_paths_are_exact_and_present() -> None: @@ -28,16 +30,17 @@ def test_origin_paths_are_exact_and_present() -> None: assert origin["public_pseudonym"] == "Attractor Set" for relative in origin["derived_paths"]: assert (ROOT / relative).exists(), relative - assert "src/aset_seed" not in origin["derived_paths"] def test_manifest_covers_repository_control_files() -> None: manifest = json.loads((ROOT / "MANIFEST.json").read_text(encoding="utf-8")) paths = {entry["path"] for entry in manifest["files"]} assert ".github/workflows/ci.yml" in paths - assert ".github/workflows/update-canon-lock.yml" in paths + assert ".github/workflows/check-canon-compatibility.yml" in paths assert ".gitignore" in paths assert "ORIGIN.json" in paths + assert "canon.lock.json" in paths + assert "src/aset_python_sqlite/seed_binding.py" in paths def test_profile_traceability_validator_passes() -> None: diff --git a/tests/test_runtime.py b/tests/test_runtime.py index 803aa7c..399decf 100644 --- a/tests/test_runtime.py +++ b/tests/test_runtime.py @@ -2,197 +2,201 @@ import copy import json -import os import sqlite3 from concurrent.futures import ThreadPoolExecutor from pathlib import Path import pytest -from aset_python_sqlite import DurableSeedRuntime, HmacSha256ProofVerifier, RejectAllProofVerifier -from aset_python_sqlite.jsonio import StrictJsonError, loads_strict -from aset_python_sqlite.proofs import sign_transition_hmac +from aset_python_sqlite import kernel +from aset_python_sqlite.runtime import DurableSeedRuntime +from aset_python_sqlite.seed_binding import ( + CANON_PACKAGE_DIGEST, + CONFORMANCE_KIT_SHA256, + PROFILE_ID, + STANDARD_ID, +) from aset_python_sqlite.store import StoreError +from tests.helpers import evaluate_operation, initial_store, register_operation, seed_objects -ROOT = Path(__file__).resolve().parents[1] -CASE = ROOT / "tests/fixtures/POS-001.json" -SECRET = b"rc12-test-secret-material-32-bytes!!" - - -def case(): - return json.loads(CASE.read_text(encoding="utf-8")) - - -def signed_candidate(): - return sign_transition_hmac(case()["candidate"], SECRET) - - -def verifier(): - return HmacSha256ProofVerifier({"principal:bootstrap": SECRET}) +def row_value(database: Path, sql: str): + connection = sqlite3.connect(database) + try: + return connection.execute(sql).fetchone()[0] + finally: + connection.close() -def test_default_runtime_fails_closed_on_proof(tmp_path): - data = case() - runtime = DurableSeedRuntime(tmp_path / "seed.db", proof_verifier=RejectAllProofVerifier()) - state = runtime.initialize(data["initial_genesis"]) - before = state["current_state_root"] - result = runtime.apply(state["trust_space_id"], data["candidate"]) - assert result == { - "accepted": False, - "code": "PROOF_REJECTED", - "state_changed": False, - "artifacts": [], - } - assert runtime.get_state(state["trust_space_id"])["current_state_root"] == before - assert runtime.verify_audit_chain(state["trust_space_id"]) +def test_database_is_bound_to_exact_seed_release(tmp_path: Path) -> None: + database = tmp_path / "seed.db" + runtime = DurableSeedRuntime(database) + runtime.initialize(initial_store()) + connection = sqlite3.connect(database) + try: + metadata = dict(connection.execute("SELECT key, value FROM metadata")) + finally: + connection.close() + assert metadata["profile_id"] == PROFILE_ID + assert metadata["canon_package_digest"] == CANON_PACKAGE_DIGEST + assert metadata["aset_release_commit"] == "633c130187b2a2bb42f24cfd66662d475de385d2" + assert metadata["compatibility_standard_id"] == STANDARD_ID + assert metadata["conformance_kit_sha256"] == CONFORMANCE_KIT_SHA256 -def test_hmac_transition_commits_state_and_audit_atomically(tmp_path): - data = case() - runtime = DurableSeedRuntime(tmp_path / "seed.db", proof_verifier=verifier()) - state = runtime.initialize(data["initial_genesis"]) - result = runtime.apply(state["trust_space_id"], signed_candidate()) - assert result["accepted"] is True - assert result["state_changed"] is True - after = runtime.get_state(state["trust_space_id"]) - assert after["current_state_root"] != state["current_state_root"] - runtime.validate(state["trust_space_id"]) - assert runtime.verify_audit_chain(state["trust_space_id"]) - health = runtime.health() - assert health.database_integrity == "ok" - assert health.audit_chain == "PASS" - - -def test_wrong_hmac_is_rejected_without_state_change(tmp_path): - data = case() - runtime = DurableSeedRuntime(tmp_path / "seed.db", proof_verifier=verifier()) - state = runtime.initialize(data["initial_genesis"]) - transition = copy.deepcopy(data["candidate"]) - transition["authn"]["proof_digest"] = "sha256:" + "0" * 64 - result = runtime.apply(state["trust_space_id"], transition) - assert result["code"] == "PROOF_REJECTED" - assert ( - runtime.get_state(state["trust_space_id"])["current_state_root"] - == state["current_state_root"] - ) + connection = sqlite3.connect(database) + try: + connection.execute( + "UPDATE metadata SET value=? WHERE key='compatibility_standard_id'", + ("sha256:" + "0" * 64,), + ) + connection.commit() + finally: + connection.close() + with pytest.raises(StoreError, match="seed binding mismatch"): + DurableSeedRuntime(database) -def test_reopen_and_backup_preserve_integrity(tmp_path): - data = case() +def test_persisted_store_digest_matches_store(tmp_path: Path) -> None: database = tmp_path / "seed.db" - runtime = DurableSeedRuntime(database, proof_verifier=verifier()) - state = runtime.initialize(data["initial_genesis"]) - runtime.apply(state["trust_space_id"], signed_candidate()) - expected = runtime.get_state(state["trust_space_id"])["current_state_root"] - - reopened = DurableSeedRuntime(database, proof_verifier=verifier()) - assert reopened.get_state(state["trust_space_id"])["current_state_root"] == expected - assert reopened.verify_audit_chain(state["trust_space_id"]) - - backup = tmp_path / "backup.db" - reopened.backup(backup) - connection = sqlite3.connect(backup) + runtime = DurableSeedRuntime(database) + runtime.initialize(initial_store()) + runtime.execute(register_operation()) + connection = sqlite3.connect(database) try: - assert connection.execute("PRAGMA integrity_check").fetchone()[0] == "ok" - profile = connection.execute( - "SELECT value FROM metadata WHERE key='profile_id'" - ).fetchone()[0] - assert profile == "ASET-PYTHON-SQLITE-LEARNING-V1" + store_json, store_digest = connection.execute( + "SELECT store_json, store_digest FROM seed_store WHERE singleton=1" + ).fetchone() finally: connection.close() + assert kernel.digest_value(json.loads(store_json)) == store_digest -def test_concurrent_replay_is_serialized(tmp_path): - data = case() +def test_state_change_and_audit_commit_together(tmp_path: Path) -> None: database = tmp_path / "seed.db" - runtime = DurableSeedRuntime(database, proof_verifier=verifier(), busy_timeout_ms=20000) - state = runtime.initialize(data["initial_genesis"]) - transition = signed_candidate() - - def apply_once(_): - worker = DurableSeedRuntime(database, proof_verifier=verifier(), busy_timeout_ms=20000) - return worker.apply(state["trust_space_id"], transition) + runtime = DurableSeedRuntime(database) + runtime.initialize(initial_store()) + result = runtime.execute(register_operation()) + assert result["state_changed"] is True + assert row_value(database, "SELECT revision FROM seed_store") == 1 + assert row_value(database, "SELECT COUNT(*) FROM operation_attempts") == 1 + assert runtime.verify_audit_chain() - with ThreadPoolExecutor(max_workers=6) as pool: - results = list(pool.map(apply_once, range(6))) - assert sum(result["state_changed"] for result in results) == 1 - assert all(result["accepted"] for result in results) - assert runtime.verify_audit_chain(state["trust_space_id"]) +def test_rejected_and_observer_operations_do_not_change_seed_store(tmp_path: Path) -> None: + database = tmp_path / "seed.db" + runtime = DurableSeedRuntime(database) + runtime.initialize(initial_store()) + runtime.execute(register_operation()) + before = row_value(database, "SELECT store_digest FROM seed_store") + + rejected = runtime.execute(register_operation()) + assert rejected["accepted"] is False + assert rejected["state_changed"] is False + assert row_value(database, "SELECT store_digest FROM seed_store") == before + + observed = runtime.execute(evaluate_operation()) + assert observed["accepted"] is True + assert observed["state_changed"] is False + assert row_value(database, "SELECT store_digest FROM seed_store") == before + assert row_value(database, "SELECT revision FROM seed_store") == 1 + assert row_value(database, "SELECT COUNT(*) FROM operation_attempts") == 3 + assert runtime.verify_audit_chain() + + +def test_audit_chain_detects_tampering(tmp_path: Path) -> None: + database = tmp_path / "seed.db" + runtime = DurableSeedRuntime(database) + runtime.initialize(initial_store()) + runtime.execute(register_operation()) + assert runtime.verify_audit_chain() + connection = sqlite3.connect(database) + try: + connection.execute("UPDATE operation_attempts SET code='TAMPERED'") + connection.commit() + finally: + connection.close() + assert not runtime.verify_audit_chain() -def test_strict_json_rejects_duplicate_members(): - with pytest.raises(StrictJsonError): - loads_strict('{"x":1,"x":2}') +def test_external_recognition_is_audited_but_not_seed_state(tmp_path: Path) -> None: + objects = seed_objects() + request = copy.deepcopy(objects["request"]) + request["resolution_id"] = "res.2" + external = "sha256:" + "d" * 64 + request["previous_terminal_record_digest"] = external + request.pop("request_digest") + request["request_digest"] = kernel.digest_value(request) -def test_store_profile_mismatch_is_rejected(tmp_path): database = tmp_path / "seed.db" - runtime = DurableSeedRuntime(database, proof_verifier=verifier()) - runtime.initialize(case()["initial_genesis"]) + runtime = DurableSeedRuntime(database) + runtime.initialize(initial_store()) + result = runtime.execute( + register_operation(request), + recognized_terminal_record_digests=[external], + ) + assert result["accepted"] is True + persisted_store = runtime.get_store() + assert "recognized_terminal_record_digests" not in persisted_store + assert persisted_store["requests"][0]["previous_terminal_record_digest"] == external + connection = sqlite3.connect(database) try: - connection.execute("UPDATE metadata SET value='wrong' WHERE key='profile_id'") - connection.commit() + environment_json = connection.execute( + "SELECT environment_json FROM operation_attempts" + ).fetchone()[0] finally: connection.close() - with pytest.raises(StoreError): - DurableSeedRuntime(database, proof_verifier=verifier()) + assert json.loads(environment_json) == {"recognized_terminal_record_digests": [external]} + assert runtime.verify_audit_chain() -def test_initialize_is_idempotent_without_nested_connection(tmp_path): - data = case() - runtime = DurableSeedRuntime(tmp_path / "seed.db", proof_verifier=verifier()) - first = runtime.initialize(data["initial_genesis"]) - second = runtime.initialize(data["initial_genesis"]) - assert second == first +def test_reopen_and_backup_preserve_store_and_binding(tmp_path: Path) -> None: + database = tmp_path / "seed.db" + runtime = DurableSeedRuntime(database) + runtime.initialize(initial_store()) + runtime.execute(register_operation()) + expected_store = runtime.get_store() + reopened = DurableSeedRuntime(database) + assert reopened.get_store() == expected_store + assert reopened.health().seed_binding == "PASS" -def test_backup_refuses_to_overwrite_existing_file(tmp_path): - runtime = DurableSeedRuntime(tmp_path / "seed.db", proof_verifier=verifier()) - runtime.initialize(case()["initial_genesis"]) backup = tmp_path / "backup.db" - backup.write_text("do not overwrite", encoding="utf-8") - with pytest.raises(StoreError): - runtime.backup(backup) - assert backup.read_text(encoding="utf-8") == "do not overwrite" + reopened.backup(backup) + copied = DurableSeedRuntime(backup) + assert copied.get_store() == expected_store + assert copied.health().database_integrity == "ok" + assert copied.health().audit_chain == "PASS" -def test_audit_chain_rejects_redundant_column_tampering(tmp_path): - data = case() +def test_concurrent_writers_are_serialized(tmp_path: Path) -> None: database = tmp_path / "seed.db" - runtime = DurableSeedRuntime(database, proof_verifier=verifier()) - state = runtime.initialize(data["initial_genesis"]) - runtime.apply(state["trust_space_id"], signed_candidate()) - connection = sqlite3.connect(database) - try: - connection.execute("UPDATE transition_attempts SET accepted=0") - connection.commit() - finally: - connection.close() - assert not runtime.verify_audit_chain(state["trust_space_id"]) + runtime = DurableSeedRuntime(database, busy_timeout_ms=20000) + runtime.initialize(initial_store()) + operation = register_operation() + + def run_once(_: int) -> dict: + worker = DurableSeedRuntime(database, busy_timeout_ms=20000) + return worker.execute(operation) + + with ThreadPoolExecutor(max_workers=6) as pool: + results = list(pool.map(run_once, range(6))) + assert sum(bool(result["state_changed"]) for result in results) == 1 + assert {result["code"] for result in results} <= {"REQUEST_REGISTERED", "RESOLUTION_ID_NOT_FRESH"} + assert row_value(database, "SELECT COUNT(*) FROM operation_attempts") == 6 + assert runtime.verify_audit_chain() -def test_audit_chain_is_bound_to_current_state_revision(tmp_path): - data = case() +def test_corrupted_store_is_not_returned(tmp_path: Path) -> None: database = tmp_path / "seed.db" - runtime = DurableSeedRuntime(database, proof_verifier=verifier()) - state = runtime.initialize(data["initial_genesis"]) - runtime.apply(state["trust_space_id"], signed_candidate()) + runtime = DurableSeedRuntime(database) + runtime.initialize(initial_store()) connection = sqlite3.connect(database) try: - connection.execute("UPDATE trust_spaces SET revision=0") + connection.execute("UPDATE seed_store SET store_json='{}'") connection.commit() finally: connection.close() - assert not runtime.verify_audit_chain(state["trust_space_id"]) - - -def test_existing_database_must_be_private_on_posix(tmp_path): - if os.name != "posix": - pytest.skip("POSIX permission profile only") - database = tmp_path / "seed.db" - runtime = DurableSeedRuntime(database, proof_verifier=verifier()) - runtime.initialize(case()["initial_genesis"]) - os.chmod(database, 0o644) - with pytest.raises(StoreError): - DurableSeedRuntime(database, proof_verifier=verifier()) + with pytest.raises(StoreError, match="validation failed"): + runtime.get_store() + assert runtime.health().store_validation == "FAIL" diff --git a/tests/test_seed_binding.py b/tests/test_seed_binding.py new file mode 100644 index 0000000..d482f6c --- /dev/null +++ b/tests/test_seed_binding.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +import json +from pathlib import Path + +from aset_python_sqlite.seed_binding import binding_document + +ROOT = Path(__file__).resolve().parents[1] + + +def test_seed_binding_constants_match_canon_lock() -> None: + lock = json.loads((ROOT / "canon.lock.json").read_text(encoding="utf-8")) + expected = binding_document() + for key in ( + "canon_id", + "canon_version", + "conformance_protocol", + "required_package_digest", + "implementation_precedence", + "source", + "standard", + ): + assert lock[key] == expected[key] diff --git a/tools/blackbox_release_audit.py b/tools/blackbox_release_audit.py index 1c2713b..a781837 100755 --- a/tools/blackbox_release_audit.py +++ b/tools/blackbox_release_audit.py @@ -5,7 +5,6 @@ import hashlib import json import os -import shutil import subprocess import sys import tempfile @@ -14,6 +13,12 @@ from typing import Any EXPECTED_ROOT = "aset-python-sqlite" +EXPECTED_VERSION = "0.1.0" +EXPECTED_STANDARD = "ASET-SEED-COMPATIBILITY-STANDARD@seed-0.3.0-alpha.3" +EXPECTED_KIT_DIGEST = "sha256:5ecf9b93377a062b8772b4b4b44b4d76a0997d8ba98e8711e717456abbe583db" +EXPECTED_PROTOCOL = "ASET-SEED-RESOLUTION-CONFORMANCE-V3" +EXPECTED_PROFILE = "ASET-PYTHON-SQLITE-REFERENCE-V1" +EXPECTED_CANON_DIGEST = "sha256:c5d48a418466ea7a60fccb7161adbd5ad568174bbc9a28fc03fd7e6e77955d31" REQUIRED_DOCUMENTS = { "AUTHORS.md", "BACKGROUND_IP_NOTICE.md", @@ -30,10 +35,14 @@ "canon.lock.json", "profile/PROFILE.json", "profile/verification-map.json", + "src/aset_python_sqlite/kernel.py", + "src/aset_python_sqlite/runtime.py", + "src/aset_python_sqlite/seed_binding.py", + "src/aset_python_sqlite/store.py", } -def digest(data: bytes) -> str: +def sha256(data: bytes) -> str: return "sha256:" + hashlib.sha256(data).hexdigest() @@ -68,7 +77,7 @@ def read_archive(path: Path) -> tuple[dict[str, bytes], list[str]]: def json_object(files: dict[str, bytes], relative: str, errors: list[str]) -> dict[str, Any]: try: value = json.loads(files[relative].decode("utf-8")) - except Exception as error: # audit boundary reports exact parse class + except Exception as error: errors.append(f"invalid {relative}: {type(error).__name__}") return {} if not isinstance(value, dict): @@ -78,10 +87,8 @@ def json_object(files: dict[str, bytes], relative: str, errors: list[str]) -> di def archive_checks(files: dict[str, bytes], errors: list[str]) -> None: - missing = sorted(REQUIRED_DOCUMENTS - set(files)) - for relative in missing: + for relative in sorted(REQUIRED_DOCUMENTS - set(files)): errors.append(f"missing required document: {relative}") - if "MANIFEST.json" not in files: return manifest = json_object(files, "MANIFEST.json", errors) @@ -97,60 +104,54 @@ def archive_checks(files: dict[str, bytes], errors: list[str]) -> None: expected[entry["path"]] = entry actual_paths = set(files) - {"MANIFEST.json"} if set(expected) != actual_paths: - errors.append( - "manifest scope mismatch: " - f"missing={sorted(actual_paths - set(expected))} " - f"extra={sorted(set(expected) - actual_paths)}" - ) + errors.append("manifest scope mismatch") if manifest.get("files_count") != len(expected): errors.append("manifest files_count mismatch") for relative, entry in expected.items(): data = files.get(relative) if data is None: continue - if entry.get("sha256") != digest(data): + if entry.get("sha256") != sha256(data): errors.append(f"manifest digest mismatch: {relative}") if entry.get("size_bytes") != len(data): errors.append(f"manifest size mismatch: {relative}") def documentation_checks(files: dict[str, bytes], errors: list[str]) -> None: - if "README.md" in files: - readme = files["README.md"].decode("utf-8") - for marker in ( - "NORMATIVE=false", - "PRODUCTION_READY=false", - "https://github.com/attractor-set/ASET", - "Implementation precedence: none", - ): - if marker not in readme: - errors.append(f"README marker missing: {marker}") - if "profile/PROFILE.json" in files: - profile = json_object(files, "profile/PROFILE.json", errors) - if profile.get("normative") is not False: - errors.append("profile normative must be false") - if profile.get("production_ready") is not False: - errors.append("profile production_ready must be false") - requirements = profile.get("profile_requirements") - if not isinstance(requirements, list) or not any( - isinstance(item, dict) and item.get("id") == "ASET-PYSQL-REQ-012" - for item in requirements - ): - errors.append("release-audit requirement missing") - if "ORIGIN.json" in files: - origin = json_object(files, "ORIGIN.json", errors) - paths = origin.get("derived_paths") - if not isinstance(paths, list) or not paths: - errors.append("ORIGIN derived_paths missing") - else: - for relative in paths: - if not isinstance(relative, str): - errors.append("ORIGIN derived path must be a string") - continue - if relative not in files and not any( - path.startswith(relative.rstrip("/") + "/") for path in files - ): - errors.append(f"ORIGIN derived path does not exist: {relative}") + readme = files.get("README.md", b"").decode("utf-8", errors="replace") + for marker in ( + "NORMATIVE=false", + "PRODUCTION_READY=false", + "ASET-PYTHON-SQLITE-REFERENCE-V1", + "seed-0.3.0-alpha.3", + "ASET-SEED-COMPATIBILITY-STANDARD@seed-0.3.0-alpha.3", + "Implementation precedence: none", + "https://github.com/attractor-set/ASET", + ): + if marker not in readme: + errors.append(f"README marker missing: {marker}") + + profile = json_object(files, "profile/PROFILE.json", errors) + if profile.get("normative") is not False: + errors.append("profile normative must be false") + if profile.get("production_ready") is not False: + errors.append("profile production_ready must be false") + if profile.get("profile_id") != EXPECTED_PROFILE: + errors.append("profile_id mismatch") + binding = profile.get("seed_binding") + if not isinstance(binding, dict) or binding.get("package_digest") != EXPECTED_CANON_DIGEST: + errors.append("profile Seed binding mismatch") + + lock = json_object(files, "canon.lock.json", errors) + if lock.get("required_package_digest") != EXPECTED_CANON_DIGEST: + errors.append("canon lock digest mismatch") + if lock.get("conformance_protocol") != EXPECTED_PROTOCOL: + errors.append("canon lock protocol mismatch") + standard = lock.get("standard", {}) + if standard.get("standard_id") != EXPECTED_STANDARD: + errors.append("compatibility standard mismatch") + if standard.get("conformance_kit_sha256") != EXPECTED_KIT_DIGEST: + errors.append("Conformance Kit digest mismatch") def extract(files: dict[str, bytes], destination: Path) -> Path: @@ -162,13 +163,7 @@ def extract(files: dict[str, bytes], destination: Path) -> Path: return root -def run( - command: list[str], - *, - cwd: Path, - input_text: str | None = None, - env: dict[str, str] | None = None, -) -> subprocess.CompletedProcess[str]: +def run(command: list[str], *, cwd: Path, input_text: str | None = None, env: dict[str, str] | None = None) -> subprocess.CompletedProcess[str]: return subprocess.run( command, cwd=cwd, @@ -186,23 +181,14 @@ def runtime_checks(files: dict[str, bytes], wheel: Path, errors: list[str]) -> N return with tempfile.TemporaryDirectory(prefix="aset-pysql-blackbox-") as temp_name: temp = Path(temp_name) - source = extract(files, temp / "source") + extract(files, temp / "source") target = temp / "site" install = run( - [ - sys.executable, - "-m", - "pip", - "install", - "--no-deps", - "--target", - str(target), - str(wheel.resolve()), - ], + [sys.executable, "-m", "pip", "install", "--no-deps", "--target", str(target), str(wheel.resolve())], cwd=temp, ) if install.returncode: - errors.append(f"wheel installation failed: {install.stderr.strip()}") + errors.append("wheel installation failed") return environment = dict(os.environ) @@ -211,38 +197,29 @@ def runtime_checks(files: dict[str, bytes], wheel: Path, errors: list[str]) -> N [ sys.executable, "-c", - ( - "import pathlib, aset_python_sqlite; " - "print(aset_python_sqlite.__version__); " - "print(pathlib.Path(aset_python_sqlite.__file__).resolve())" - ), + "import aset_python_sqlite as a; print(a.__version__); print(a.CANON_PACKAGE_DIGEST); print(a.ASET_RELEASE_COMMIT); print(a.STANDARD_ID); print(a.CONFORMANCE_KIT_SHA256)", ], cwd=temp, env=environment, ) - import_lines = import_check.stdout.strip().splitlines() - if ( - import_check.returncode - or len(import_lines) != 2 - or import_lines[0] != "0.1.0" - or target.resolve() not in Path(import_lines[1]).parents - ): - errors.append("installed package import/version/path check failed") - - describe_request = json.dumps( - { - "protocol": "ASET-IMPLEMENTATION-CONFORMANCE-V1", - "operation": "describe", - } - ) + lines = import_check.stdout.strip().splitlines() + if import_check.returncode or lines != [ + EXPECTED_VERSION, + EXPECTED_CANON_DIGEST, + "633c130187b2a2bb42f24cfd66662d475de385d2", + EXPECTED_STANDARD, + EXPECTED_KIT_DIGEST, + ]: + errors.append("installed package Seed binding check failed") + describe = run( [sys.executable, "-m", "aset_python_sqlite.adapter"], cwd=temp, - input_text=describe_request, + input_text=json.dumps({"protocol": EXPECTED_PROTOCOL, "operation": "describe"}), env=environment, ) if describe.returncode: - errors.append(f"installed adapter describe failed: {describe.stderr.strip()}") + errors.append("installed adapter describe failed") else: try: response = json.loads(describe.stdout) @@ -250,110 +227,93 @@ def runtime_checks(files: dict[str, bytes], wheel: Path, errors: list[str]) -> N errors.append("installed adapter describe returned invalid JSON") else: implementation = response.get("implementation", {}) + if implementation.get("profile_id") != EXPECTED_PROFILE: + errors.append("installed adapter profile mismatch") if implementation.get("normative") is not False: errors.append("installed adapter claims normative status") - if "verdict" in response or "pass" in response: + if "pass" in response or "verdict" in response: errors.append("installed adapter self-declares conformance") - fixture_path = source / "tests/fixtures/POS-001.json" - if not fixture_path.is_file(): - errors.append("positive fixture missing from source archive") - else: - case = json.loads(fixture_path.read_text(encoding="utf-8")) - execute_request = json.dumps( - { - "protocol": "ASET-IMPLEMENTATION-CONFORMANCE-V1", - "operation": "execute_case", - "case": case, - } - ) - execute = run( - [sys.executable, "-m", "aset_python_sqlite.adapter"], - cwd=temp, - input_text=execute_request, - env=environment, - ) - if execute.returncode: - errors.append(f"installed adapter execute failed: {execute.stderr.strip()}") - else: - try: - response = json.loads(execute.stdout) - except json.JSONDecodeError: - errors.append("installed adapter execute returned invalid JSON") - else: - if response.get("actual") != case.get("expected"): - errors.append("installed adapter positive observation mismatch") - if "verdict" in response or "pass" in response: - errors.append("installed adapter execute self-declares conformance") - - help_result = run( - [sys.executable, "-m", "aset_python_sqlite", "--help"], - cwd=temp, - env=environment, - ) - if help_result.returncode or "aset-python-sqlite" not in help_result.stdout: - errors.append("installed CLI help check failed") - + smoke_script = r''' +from pathlib import Path +from aset_python_sqlite import DurableSeedRuntime, digest_value -def write_reports(output_json: Path, output_md: Path, errors: list[str], archive: Path, wheel: Path) -> None: - report = { - "document_type": "aset-python-sqlite-blackbox-release-audit", - "archive": archive.name, - "archive_sha256": digest(archive.read_bytes()), - "wheel": wheel.name, - "wheel_sha256": digest(wheel.read_bytes()) if wheel.is_file() else None, - "findings": errors, - "verdict": "PASS" if not errors else "FAIL", - } - output_json.parent.mkdir(parents=True, exist_ok=True) - output_json.write_text(json.dumps(report, sort_keys=True, indent=2) + "\n", encoding="utf-8") - lines = [ - "# aset-python-sqlite black-box release audit", - "", - f"- Archive: `{report['archive']}`", - f"- Archive SHA-256: `{report['archive_sha256']}`", - f"- Wheel: `{report['wheel']}`", - f"- Wheel SHA-256: `{report['wheel_sha256']}`", - f"- Verdict: **{report['verdict']}**", - "", - "## Findings", - "", - ] - lines.extend(f"- {finding}" for finding in errors) - if not errors: - lines.append("- None.") - output_md.write_text("\n".join(lines) + "\n", encoding="utf-8") +binding = { + "context_id": "ctx.blackbox", + "policy_epoch": 1, + "question_digest": "sha256:" + "b" * 64, + "scope": ["effect:test"], + "state_root": "sha256:" + "a" * 64, +} +binding["binding_digest"] = digest_value(binding) +authority = { + "authority_id": "authority.blackbox", + "binding_digest": binding["binding_digest"], + "context_id": binding["context_id"], + "policy_epoch": binding["policy_epoch"], +} +authority["authority_binding_digest"] = digest_value(authority) +request = { + "binding": binding, + "initial_authority_binding_digest": authority["authority_binding_digest"], + "previous_terminal_record_digest": None, + "resolution_id": "res.blackbox", +} +request["request_digest"] = digest_value(request) +store = {"requests": [], "records": [], "authority_bindings": [authority]} +runtime = DurableSeedRuntime(Path("blackbox.db")) +runtime.initialize(store) +result = runtime.execute({"kind": "REGISTER_REQUEST", "payload": {"request": request}}) +assert result["code"] == "REQUEST_REGISTERED" +assert result["resolution"] == "UNKNOWN" +assert runtime.health().audit_chain == "PASS" +print("BLACKBOX_RUNTIME_SMOKE=PASS") +''' + smoke = run([sys.executable, "-c", smoke_script], cwd=temp, env=environment) + if smoke.returncode or "BLACKBOX_RUNTIME_SMOKE=PASS" not in smoke.stdout: + errors.append("installed runtime smoke test failed") def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("archive", type=Path) parser.add_argument("--wheel", type=Path, required=True) - parser.add_argument( - "--output-json", - type=Path, - default=Path("dist/blackbox-release-audit.json"), - ) - parser.add_argument( - "--output-md", - type=Path, - default=Path("dist/blackbox-release-audit.md"), - ) args = parser.parse_args() errors: list[str] = [] try: files, archive_errors = read_archive(args.archive) - except (OSError, zipfile.BadZipFile) as error: + errors.extend(archive_errors) + except Exception as error: files = {} - archive_errors = [f"archive unreadable: {type(error).__name__}"] - errors.extend(archive_errors) + errors.append(f"archive read failed: {type(error).__name__}") + archive_checks(files, errors) documentation_checks(files, errors) runtime_checks(files, args.wheel, errors) - write_reports(args.output_json, args.output_md, errors, args.archive, args.wheel) - print(f"BLACKBOX_RELEASE_FINDINGS={len(errors)}") + report = { + "document_type": "aset-python-sqlite-blackbox-release-audit", + "archive": str(args.archive), + "wheel": str(args.wheel), + "findings": errors, + "pass": not errors, + } + dist = Path("dist") + dist.mkdir(exist_ok=True) + (dist / "blackbox-release-audit.json").write_text( + json.dumps(report, sort_keys=True, indent=2) + "\n", + encoding="utf-8", + ) + lines = ["# Black-box release audit", "", f"Verdict: {'PASS' if not errors else 'FAIL'}", ""] + if errors: + lines.extend(f"- {error}" for error in errors) + else: + lines.append("No findings.") + (dist / "blackbox-release-audit.md").write_text("\n".join(lines) + "\n", encoding="utf-8") + + for error in errors: + print("BLACKBOX_RELEASE_AUDIT_ERROR=" + error) print("BLACKBOX_RELEASE_AUDIT=" + ("PASS" if not errors else "FAIL")) return 0 if not errors else 1 diff --git a/tools/build_release.py b/tools/build_release.py index c666444..b96cf76 100755 --- a/tools/build_release.py +++ b/tools/build_release.py @@ -12,6 +12,8 @@ FIXED_TIMESTAMP = (1980, 1, 1, 0, 0, 0) EXCLUDED_PARTS = { ".aset-spec", + ".aset-standard", + ".aset-standard-assets", ".git", ".mypy_cache", ".pytest_cache", diff --git a/tools/profile_gate.py b/tools/profile_gate.py index ea5ae05..19e3ca6 100755 --- a/tools/profile_gate.py +++ b/tools/profile_gate.py @@ -2,6 +2,7 @@ from __future__ import annotations import argparse +import hashlib import json import shutil import subprocess @@ -13,15 +14,33 @@ def run(arguments: list[str]) -> None: - result = subprocess.run( - [sys.executable, *arguments], - cwd=ROOT, - check=False, - ) + result = subprocess.run([sys.executable, *arguments], cwd=ROOT, check=False) if result.returncode: raise SystemExit(result.returncode) +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for block in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(block) + return digest.hexdigest() + + +def build_source_archive_twice() -> None: + run(["tools/build_release.py"]) + archive = DIST / "aset-python-sqlite-source.zip" + first = sha256_file(archive) + run(["tools/build_release.py"]) + second = sha256_file(archive) + print("SOURCE_ARCHIVE_BUILD_1_SHA256=" + first) + print("SOURCE_ARCHIVE_BUILD_2_SHA256=" + second) + if first != second: + print("SOURCE_ARCHIVE_DETERMINISM=FAIL") + raise SystemExit(2) + print("SOURCE_ARCHIVE_DETERMINISM=PASS") + + def build_wheel() -> Path: wheel_dir = DIST / "wheel" if wheel_dir.exists(): @@ -47,7 +66,7 @@ def build_wheel() -> Path: wheels = sorted(wheel_dir.glob("aset_python_sqlite-*.whl")) if len(wheels) != 1: print(f"WHEEL_COUNT={len(wheels)}") - raise SystemExit(1) + raise SystemExit(2) print(f"WHEEL={wheels[0]}") print("WHEEL_BUILD=PASS") return wheels[0] @@ -62,6 +81,9 @@ def main() -> int: if profile["normative"] is not False or profile["production_ready"] is not False: print("PROFILE_STATUS=INVALID") return 1 + if profile.get("semantic_authority") != "EXTERNAL_ASET_CANON": + print("PROFILE_SEMANTIC_AUTHORITY=INVALID") + return 1 run(["tools/rebuild_manifest.py", "--check"]) run(["tools/validate_profile_traceability.py"]) @@ -76,7 +98,7 @@ def main() -> int: "--canon-root", str(canon_root), "--adapter", - f'{sys.executable} {ROOT / "tools/adapter_entry.py"}', + f"{sys.executable} {ROOT / 'tools/adapter_entry.py'}", "--adapter-cwd", str(ROOT), "--output", @@ -84,7 +106,7 @@ def main() -> int: ] ) - run(["tools/build_release.py"]) + build_source_archive_twice() wheel = build_wheel() run( [ diff --git a/tools/rebuild_manifest.py b/tools/rebuild_manifest.py index eacd19b..0790b0f 100644 --- a/tools/rebuild_manifest.py +++ b/tools/rebuild_manifest.py @@ -5,7 +5,7 @@ import json from pathlib import Path ROOT=Path(__file__).resolve().parents[1] -EXCLUDED={'.aset-spec','.git','.venv','__pycache__','.pytest_cache','.ruff_cache','.mypy_cache','.tox','dist','build'} +EXCLUDED={'.aset-spec','.aset-standard','.aset-standard-assets','.git','.venv','__pycache__','.pytest_cache','.ruff_cache','.mypy_cache','.tox','dist','build'} def included(path:Path)->bool: return (path.as_posix() != '.coverage' and not any(part in EXCLUDED or part.endswith('.egg-info') for part in path.parts) and path.name != 'MANIFEST.json') def expected(): diff --git a/tools/validate_profile_traceability.py b/tools/validate_profile_traceability.py index 8c51531..f14f8ec 100755 --- a/tools/validate_profile_traceability.py +++ b/tools/validate_profile_traceability.py @@ -104,7 +104,7 @@ def main() -> int: if error: errors.append(f"{category} {identifier}: {error}") - release_checks = set(requirement_map.get("ASET-PYSQL-REQ-012", [])) + release_checks = set(requirement_map.get("ASET-PYSQL-REQ-009", [])) required_release_checks = { "blackbox-release-audit", "manifest-check", @@ -113,7 +113,7 @@ def main() -> int: } if not required_release_checks.issubset(release_checks): errors.append( - "ASET-PYSQL-REQ-012 does not bind the complete release gate: " + "ASET-PYSQL-REQ-009 does not bind the complete release gate: " f"missing={sorted(required_release_checks - release_checks)}" ) diff --git a/tools/verify_canon_lock.py b/tools/verify_canon_lock.py old mode 100644 new mode 100755 index 46362c4..4013e11 --- a/tools/verify_canon_lock.py +++ b/tools/verify_canon_lock.py @@ -1,20 +1,160 @@ #!/usr/bin/env python3 from __future__ import annotations -import argparse,json + +import argparse +import hashlib +import json +import subprocess from pathlib import Path +from typing import Any + +from aset_python_sqlite.seed_binding import binding_document + +ROOT = Path(__file__).resolve().parents[1] +STANDARD_PROFILE_REPO_PATH = Path( + "standards/seed-compatibility/compatibility-standard-profile-v1.json" +) + + +def load(path: Path) -> dict[str, Any]: + value = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(value, dict): + raise ValueError(f"{path} must contain an object") + return value + + +def sha256(path: Path) -> str: + return "sha256:" + hashlib.sha256(path.read_bytes()).hexdigest() + + +def compare(errors: list[str], label: str, actual: Any, expected: Any) -> None: + if actual != expected: + errors.append(label) + def main() -> int: - ap=argparse.ArgumentParser(); ap.add_argument('--canon-root',type=Path,required=True); a=ap.parse_args() - root=Path(__file__).resolve().parents[1] - lock=json.loads((root/'canon.lock.json').read_text(encoding='utf-8')) - package=json.loads((a.canon_root/'seed/canonical/CANON_PACKAGE.json').read_text(encoding='utf-8')) - errors=[] - for key,source in [('canon_id','canon_id'),('canon_version','canon_version'),('conformance_protocol','conformance_protocol')]: - if lock[key]!=package[source]: errors.append(key) - if lock['required_package_digest']!=package['package_digest']: errors.append('package_digest') - if package.get('implementation_precedence')!='NONE': errors.append('implementation_precedence') + parser = argparse.ArgumentParser() + parser.add_argument("--canon-root", type=Path, required=True) + parser.add_argument("--standard-identity", type=Path) + parser.add_argument("--standard-kit", type=Path) + args = parser.parse_args() + + lock = load(ROOT / "canon.lock.json") + expected = binding_document() + canon_root = args.canon_root.resolve() + package = load(canon_root / "seed/canonical/CANON_PACKAGE.json") + protocol = load(canon_root / "seed/canonical/conformance/implementation-conformance-protocol.json") + conformance_profile_path = canon_root / "seed/canonical/conformance/conformance-profile.json" + conformance_profile = load(conformance_profile_path) + errors: list[str] = [] + + for key in ( + "canon_id", + "canon_version", + "conformance_protocol", + "required_package_digest", + "implementation_precedence", + ): + compare(errors, f"local_binding:{key}", lock.get(key), expected.get(key)) + compare(errors, "local_binding:source", lock.get("source"), expected.get("source")) + compare(errors, "local_binding:standard", lock.get("standard"), expected.get("standard")) + + comparisons = { + "canon_id": package.get("canon_id"), + "canon_version": package.get("canon_version"), + "conformance_protocol": package.get("conformance_protocol"), + "required_package_digest": package.get("package_digest"), + "implementation_precedence": package.get("implementation_precedence"), + } + for key, actual in comparisons.items(): + compare(errors, f"canon_package:{key}", lock.get(key), actual) + + compare(errors, "conformance_protocol_document", protocol.get("protocol_id"), lock.get("conformance_protocol")) + compare(errors, "protocol_implementation_precedence", protocol.get("implementation_precedence"), "NONE") + + standard = lock.get("standard", {}) + compare( + errors, + "conformance_profile_sha256", + sha256(conformance_profile_path), + standard.get("conformance_profile_sha256"), + ) + compare( + errors, + "mandatory_conformance_cases", + conformance_profile.get("case_count"), + standard.get("mandatory_conformance_cases"), + ) + + kit_profile_path = canon_root / "STANDARD-PROFILE.json" + repo_profile_path = canon_root / STANDARD_PROFILE_REPO_PATH + standard_profile_path = kit_profile_path if kit_profile_path.is_file() else repo_profile_path + if not standard_profile_path.is_file(): + errors.append("standard_profile_missing") + else: + profile = load(standard_profile_path) + compare(errors, "standard_profile_id", profile.get("profile_id"), standard.get("standard_profile_id")) + compare(errors, "standard_series_id", profile.get("standard_series_id"), standard.get("standard_series_id")) + compare(errors, "standard_profile_sha256", sha256(standard_profile_path), standard.get("standard_profile_sha256")) + + identity_path = args.standard_identity + embedded_identity = canon_root / "STANDARD.json" + if identity_path is None and embedded_identity.is_file(): + identity_path = embedded_identity + if identity_path is not None: + identity = load(identity_path) + identity_expected = { + "standard_id": standard.get("standard_id"), + "standard_series_id": standard.get("standard_series_id"), + "standard_profile_id": standard.get("standard_profile_id"), + "standard_profile_sha256": standard.get("standard_profile_sha256"), + "release_tag": lock["source"]["tag"], + "release_commit": lock["source"]["ref"], + "release_version": standard.get("release_version"), + "seed_semantic_version": standard.get("seed_semantic_version"), + "canon_id": lock.get("canon_id"), + "canon_version": lock.get("canon_version"), + "canonical_package_digest": lock.get("required_package_digest"), + "conformance_protocol": lock.get("conformance_protocol"), + "conformance_profile_sha256": standard.get("conformance_profile_sha256"), + "mandatory_conformance_cases": standard.get("mandatory_conformance_cases"), + "implementation_precedence": "NONE", + "verdict_authority": standard.get("verdict_authority"), + } + for key, value in identity_expected.items(): + compare(errors, f"standard_identity:{key}", identity.get(key), value) + + if args.standard_kit is not None: + compare(errors, "conformance_kit_sha256", sha256(args.standard_kit), standard.get("conformance_kit_sha256")) + + git_dir = canon_root / ".git" + if git_dir.exists(): + completed = subprocess.run( + ["git", "-C", str(canon_root), "rev-parse", "HEAD"], + text=True, + capture_output=True, + check=False, + ) + if completed.returncode or completed.stdout.strip() != lock["source"]["ref"]: + errors.append("source_ref") + if errors: - for e in errors: print('CANON_LOCK_ERROR='+e) + for error in errors: + print("CANON_LOCK_ERROR=" + error) + print("CANON_LOCK_VERIFICATION=FAIL") return 1 - print('CANON_LOCK_VERIFICATION=PASS'); print('CANON_PACKAGE_DIGEST='+package['package_digest']); return 0 -if __name__=='__main__': raise SystemExit(main()) + + print("CANON_LOCK_VERIFICATION=PASS") + print("SEED_COMPATIBILITY_STANDARD=" + standard["standard_id"]) + print("SEED_COMPATIBILITY_STANDARD_PROFILE=" + standard["standard_profile_id"]) + print("SEED_CONFORMANCE_KIT_SHA256=" + standard["conformance_kit_sha256"]) + print("CANON_ID=" + lock["canon_id"]) + print("CANON_VERSION=" + lock["canon_version"]) + print("CANON_PACKAGE_DIGEST=" + lock["required_package_digest"]) + print("ASET_RELEASE_TAG=" + lock["source"]["tag"]) + print("ASET_RELEASE_COMMIT=" + lock["source"]["ref"]) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())