Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: build test validate validate-portable-ai dist release-dry-run clean
.PHONY: build test validate validate-portable-ai validate-model-carry-boundary dist release-dry-run clean

BIN := sourceos-ai
DIST_DIR := dist
Expand All @@ -20,7 +20,10 @@ test:
validate-portable-ai:
python3 tools/validate_portable_ai_packs.py

validate: build validate-portable-ai
validate-model-carry-boundary:
python3 tools/validate_model_carry_authorization_boundaries.py

validate: build validate-portable-ai validate-model-carry-boundary
python3 tools/validate_carry_refs.py
bin/$(BIN) carry validate --refs examples
bin/$(BIN) validate --refs examples
Expand Down
72 changes: 72 additions & 0 deletions contracts/model-carry-authorization-boundary.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.srcos.ai/model-carry/model-carry-authorization-boundary.schema.json",
"title": "SourceOS Model Carry Authorization Boundary",
"description": "Decision-only boundary proving a carry profile is a reference/profile object, not authorization for runtime execution, prompt egress, tool use, model download, training, promotion, or lifecycle mutation.",
"type": "object",
"additionalProperties": false,
"required": [
"schemaVersion",
"kind",
"boundaryId",
"profileRef",
"profileKind",
"carryScope",
"authorizationBoundary",
"decisionRefs",
"evidenceRefs"
],
"properties": {
"schemaVersion": { "const": "v0.1" },
"kind": { "const": "ModelCarryAuthorizationBoundary" },
"boundaryId": { "type": "string", "pattern": "^urn:srcos:model-carry-boundary:" },
"profileRef": { "type": "string", "pattern": "^urn:srcos:model-profile:" },
"profileKind": { "const": "LocalModelProfile" },
"carryScope": {
"type": "object",
"additionalProperties": false,
"required": ["mayCarryProfile", "mayCarryServiceRef", "mayEmitEvidence", "mayRouteLocally"],
"properties": {
"mayCarryProfile": { "const": true },
"mayCarryServiceRef": { "const": true },
"mayEmitEvidence": { "const": true },
"mayRouteLocally": { "type": "boolean" }
}
},
"authorizationBoundary": {
"type": "object",
"additionalProperties": false,
"required": [
"authorizesPromptEgress",
"authorizesNetworkAccess",
"authorizesToolUse",
"authorizesModelDownload",
"authorizesTrainingOnUserData",
"authorizesModelPromotion",
"authorizesLifecycleMutation"
],
"properties": {
"authorizesPromptEgress": { "const": false },
"authorizesNetworkAccess": { "const": false },
"authorizesToolUse": { "const": false },
"authorizesModelDownload": { "const": false },
"authorizesTrainingOnUserData": { "const": false },
"authorizesModelPromotion": { "const": false },
"authorizesLifecycleMutation": { "const": false }
}
},
"decisionRefs": {
"type": "object",
"additionalProperties": false,
"required": ["policyDecisionRefs", "routerDecisionRefs", "governanceLedgerRefs", "explicitPullRefs"],
"properties": {
"policyDecisionRefs": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
"routerDecisionRefs": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
"governanceLedgerRefs": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
"explicitPullRefs": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }
}
},
"evidenceRefs": { "type": "array", "minItems": 1, "items": { "type": "string" }, "uniqueItems": true },
"notes": { "type": "string" }
}
}
45 changes: 45 additions & 0 deletions docs/model-carry-authorization-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Model Carry Authorization Boundary

## Purpose

`ModelCarryAuthorizationBoundary` proves that a SourceOS local model profile is a carry-layer reference object, not an authorization object.

The model carry repo may carry profiles, service refs, launch hints, cache policy, and evidence expectations. It does not authorize prompt egress, network access, tool use, model download, training on user data, model promotion, or model lifecycle mutation.

## Boundary chain

```text
local model profile = carry/reference object
model router = routing decision
policy fabric = prompt egress / network / tool-use admission
model governance ledger = lifecycle, tuning, promotion, consent, revocation evidence
explicit pull/install = separate operator action
```

## Required false authorizations

A valid boundary record must set all of these to `false`:

```text
authorizesPromptEgress
authorizesNetworkAccess
authorizesToolUse
authorizesModelDownload
authorizesTrainingOnUserData
authorizesModelPromotion
authorizesLifecycleMutation
```

## Validation

```bash
python3 tools/validate_model_carry_authorization_boundaries.py
```

The validator checks one valid boundary fixture and negative fixtures for prompt egress and automatic model download.

## Non-goals

This tranche does not implement model execution, router decisions, model download, prompt egress, network access, tool access, personal tuning, or model promotion.

It only hardens the carry-layer contract so future implementation work cannot treat a profile as authorization.
30 changes: 30 additions & 0 deletions examples/model-carry-authorization-boundary.download.invalid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"schemaVersion": "v0.1",
"kind": "ModelCarryAuthorizationBoundary",
"boundaryId": "urn:srcos:model-carry-boundary:invalid-download",
"profileRef": "urn:srcos:model-profile:local-llama32-1b",
"profileKind": "LocalModelProfile",
"carryScope": {
"mayCarryProfile": true,
"mayCarryServiceRef": true,
"mayEmitEvidence": true,
"mayRouteLocally": true
},
"authorizationBoundary": {
"authorizesPromptEgress": false,
"authorizesNetworkAccess": false,
"authorizesToolUse": false,
"authorizesModelDownload": true,
"authorizesTrainingOnUserData": false,
"authorizesModelPromotion": false,
"authorizesLifecycleMutation": false
},
"decisionRefs": {
"policyDecisionRefs": [],
"routerDecisionRefs": [],
"governanceLedgerRefs": [],
"explicitPullRefs": []
},
"evidenceRefs": ["evidence://sourceos-model-carry/invalid/model-download"],
"notes": "Invalid fixture: carry profile must not authorize model download."
}
30 changes: 30 additions & 0 deletions examples/model-carry-authorization-boundary.local-llama32-1b.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"schemaVersion": "v0.1",
"kind": "ModelCarryAuthorizationBoundary",
"boundaryId": "urn:srcos:model-carry-boundary:local-llama32-1b",
"profileRef": "urn:srcos:model-profile:local-llama32-1b",
"profileKind": "LocalModelProfile",
"carryScope": {
"mayCarryProfile": true,
"mayCarryServiceRef": true,
"mayEmitEvidence": true,
"mayRouteLocally": true
},
"authorizationBoundary": {
"authorizesPromptEgress": false,
"authorizesNetworkAccess": false,
"authorizesToolUse": false,
"authorizesModelDownload": false,
"authorizesTrainingOnUserData": false,
"authorizesModelPromotion": false,
"authorizesLifecycleMutation": false
},
"decisionRefs": {
"policyDecisionRefs": [],
"routerDecisionRefs": [],
"governanceLedgerRefs": [],
"explicitPullRefs": []
},
"evidenceRefs": ["evidence://sourceos-model-carry/local-llama32-1b/profile-boundary"],
"notes": "This record proves the carry profile may be carried and routed locally, but does not authorize prompt egress, network, tool use, download, training, promotion, or lifecycle mutation."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"schemaVersion": "v0.1",
"kind": "ModelCarryAuthorizationBoundary",
"boundaryId": "urn:srcos:model-carry-boundary:invalid-prompt-egress",
"profileRef": "urn:srcos:model-profile:local-llama32-1b",
"profileKind": "LocalModelProfile",
"carryScope": {
"mayCarryProfile": true,
"mayCarryServiceRef": true,
"mayEmitEvidence": true,
"mayRouteLocally": true
},
"authorizationBoundary": {
"authorizesPromptEgress": true,
"authorizesNetworkAccess": false,
"authorizesToolUse": false,
"authorizesModelDownload": false,
"authorizesTrainingOnUserData": false,
"authorizesModelPromotion": false,
"authorizesLifecycleMutation": false
},
"decisionRefs": {
"policyDecisionRefs": [],
"routerDecisionRefs": [],
"governanceLedgerRefs": [],
"explicitPullRefs": []
},
"evidenceRefs": ["evidence://sourceos-model-carry/invalid/prompt-egress"],
"notes": "Invalid fixture: carry profile must not authorize prompt egress."
}
93 changes: 93 additions & 0 deletions tools/validate_model_carry_authorization_boundaries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""Validate SourceOS ModelCarryAuthorizationBoundary examples."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

ROOT = Path(__file__).resolve().parents[1]
SCHEMA = ROOT / "contracts" / "model-carry-authorization-boundary.schema.json"
VALID = ROOT / "examples" / "model-carry-authorization-boundary.local-llama32-1b.json"
INVALID_PROMPT_EGRESS = ROOT / "examples" / "model-carry-authorization-boundary.prompt-egress.invalid.json"
INVALID_DOWNLOAD = ROOT / "examples" / "model-carry-authorization-boundary.download.invalid.json"

FORBIDDEN_AUTHORIZATIONS = (
"authorizesPromptEgress",
"authorizesNetworkAccess",
"authorizesToolUse",
"authorizesModelDownload",
"authorizesTrainingOnUserData",
"authorizesModelPromotion",
"authorizesLifecycleMutation",
)


class ValidationError(Exception):
pass


def load_json(path: Path) -> dict[str, Any]:
payload = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(payload, dict):
raise ValidationError(f"{path.relative_to(ROOT)}: expected JSON object")
return payload


def require(condition: bool, message: str) -> None:
if not condition:
raise ValidationError(message)


def validate_schema(schema: dict[str, Any]) -> None:
require(schema.get("$schema") == "https://json-schema.org/draft/2020-12/schema", "schema draft mismatch")
require(schema.get("type") == "object", "schema must describe object")
require(schema.get("additionalProperties") is False, "schema must be closed")


def validate_boundary(path: Path, record: dict[str, Any]) -> None:
require(record.get("schemaVersion") == "v0.1", f"{path}: schemaVersion must be v0.1")
require(record.get("kind") == "ModelCarryAuthorizationBoundary", f"{path}: kind mismatch")
require(str(record.get("boundaryId", "")).startswith("urn:srcos:model-carry-boundary:"), f"{path}: boundaryId must be SourceOS boundary URN")
require(str(record.get("profileRef", "")).startswith("urn:srcos:model-profile:"), f"{path}: profileRef must point at model profile")
require(record.get("profileKind") == "LocalModelProfile", f"{path}: profileKind must be LocalModelProfile")

carry_scope = record.get("carryScope", {})
require(carry_scope.get("mayCarryProfile") is True, f"{path}: mayCarryProfile must be true")
require(carry_scope.get("mayCarryServiceRef") is True, f"{path}: mayCarryServiceRef must be true")
require(carry_scope.get("mayEmitEvidence") is True, f"{path}: mayEmitEvidence must be true")

auth = record.get("authorizationBoundary", {})
for field in FORBIDDEN_AUTHORIZATIONS:
require(auth.get(field) is False, f"{path}: carry profile must not set {field}=true")

evidence_refs = record.get("evidenceRefs", [])
require(isinstance(evidence_refs, list) and evidence_refs, f"{path}: evidenceRefs required")
for ref in evidence_refs:
require(isinstance(ref, str) and ref.startswith("evidence://"), f"{path}: evidenceRefs must use evidence:// refs")


def expect_invalid(path: Path) -> None:
try:
validate_boundary(path.relative_to(ROOT), load_json(path))
except ValidationError:
return
raise ValidationError(f"invalid fixture unexpectedly validated: {path.relative_to(ROOT)}")


def main() -> int:
try:
validate_schema(load_json(SCHEMA))
validate_boundary(VALID.relative_to(ROOT), load_json(VALID))
expect_invalid(INVALID_PROMPT_EGRESS)
expect_invalid(INVALID_DOWNLOAD)
except (OSError, json.JSONDecodeError, ValidationError) as exc:
print(f"ERR: {exc}")
return 1
print("Model carry authorization boundary validation passed")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading