palonexus 0.3.2__tar.gz → 0.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {palonexus-0.3.2 → palonexus-0.3.3}/PKG-INFO +1 -1
- palonexus-0.3.3/src/palonexus/_build.py +2 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/_sdist_provenance.py +1 -1
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/_version.py +2 -2
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/cli/commands.py +77 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/client.py +109 -0
- palonexus-0.3.3/src/palonexus/developer/evidence.py +422 -0
- palonexus-0.3.2/src/palonexus/_build.py +0 -2
- {palonexus-0.3.2 → palonexus-0.3.3}/.gitignore +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/LICENSE +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/hatch_build.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/pyproject.toml +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/__init__.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/_canonicalize.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/_generated/__init__.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/_generated/protocol.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/approvals.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/async_client.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/cli/__init__.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/cli/main.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/client.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/context.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/credentials.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/__init__.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/context.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/contracts.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/credentials.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/guard.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/runner.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/scaffold.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/subagents.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/templates/__init__.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/templates/plain_python/__init__.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/templates/plain_python/agent.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/templates/plain_python/fixtures/release-change.json +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/templates/plain_python/palonexus-agent.yaml +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/templates/plain_python/palonexus-registration.yaml +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/templates/plain_python/pyproject.toml.tmpl +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/version.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/errors.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/identity/__init__.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/identity/did.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/identity/oidc.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/identity/vc.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/integrations/__init__.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/integrations/deepagents.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/integrations/langchain.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/integrations/langgraph.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/keystore.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/models.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/protocol.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/py.typed +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/redaction.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/retry.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/testing/__init__.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/testing/fake_transport.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/testing/mock_server.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/transports/__init__.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/transports/base.py +0 -0
- {palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/transports/http.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: palonexus
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Python SDK for the PaloNexus agent control plane
|
|
5
5
|
Project-URL: Homepage, https://github.com/rogerchucker/palonexus-sdk
|
|
6
6
|
Project-URL: Repository, https://github.com/rogerchucker/palonexus-sdk
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 3,
|
|
21
|
+
__version__ = version = '0.3.3'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 3)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -36,6 +36,11 @@ from palonexus.developer.credentials import (
|
|
|
36
36
|
CredentialStore,
|
|
37
37
|
CredentialStoreUnavailable,
|
|
38
38
|
)
|
|
39
|
+
from palonexus.developer.evidence import (
|
|
40
|
+
build_preexchange_evidence,
|
|
41
|
+
build_runtime_attestation,
|
|
42
|
+
ensure_runtime_evidence_credential,
|
|
43
|
+
)
|
|
39
44
|
from palonexus.developer.runner import Runner
|
|
40
45
|
from palonexus.developer.scaffold import (
|
|
41
46
|
ScaffoldError,
|
|
@@ -1060,6 +1065,23 @@ def _strict_input(path: Path) -> tuple[dict[str, Any], str]:
|
|
|
1060
1065
|
return value, hashlib.sha256(canonical_json(value)).hexdigest()
|
|
1061
1066
|
|
|
1062
1067
|
|
|
1068
|
+
def _require_runtime_evidence_expiry(value: object) -> str:
|
|
1069
|
+
if not isinstance(value, str) or not value:
|
|
1070
|
+
raise CommandError("runtime lease is missing its evidence expiry")
|
|
1071
|
+
try:
|
|
1072
|
+
expires = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
1073
|
+
except ValueError as error:
|
|
1074
|
+
raise CommandError("runtime lease has an invalid evidence expiry") from error
|
|
1075
|
+
if expires.tzinfo is None or expires <= datetime.now(UTC):
|
|
1076
|
+
raise CommandError("runtime lease has an invalid evidence expiry")
|
|
1077
|
+
return (
|
|
1078
|
+
expires.astimezone(UTC)
|
|
1079
|
+
.replace(microsecond=0)
|
|
1080
|
+
.isoformat()
|
|
1081
|
+
.replace("+00:00", "Z")
|
|
1082
|
+
)
|
|
1083
|
+
|
|
1084
|
+
|
|
1063
1085
|
def _strict_json_pairs(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
|
1064
1086
|
value: dict[str, Any] = {}
|
|
1065
1087
|
for key, item in pairs:
|
|
@@ -1077,6 +1099,9 @@ def run_agent(args: Namespace) -> int:
|
|
|
1077
1099
|
try:
|
|
1078
1100
|
if guard is None:
|
|
1079
1101
|
guard = generate_agent_credential()
|
|
1102
|
+
evidence_guard = ensure_runtime_evidence_credential(guard)
|
|
1103
|
+
if evidence_guard != guard:
|
|
1104
|
+
guard = evidence_guard
|
|
1080
1105
|
store.save("guard:" + str(descriptor["name"]), guard)
|
|
1081
1106
|
artifact = "sha256:" + descriptor["descriptor_digest"]
|
|
1082
1107
|
# Enrollment proofs are one-time. Each process start creates a fresh
|
|
@@ -1094,6 +1119,29 @@ def run_agent(args: Namespace) -> int:
|
|
|
1094
1119
|
guard_version="pnxs/0.1.0",
|
|
1095
1120
|
)
|
|
1096
1121
|
runtime = client.redeem_runtime(session, agent, enrollment)
|
|
1122
|
+
registration_profile = _project_registration_profile()
|
|
1123
|
+
created_at = (
|
|
1124
|
+
datetime.now(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
1125
|
+
)
|
|
1126
|
+
expires_at = _require_runtime_evidence_expiry(runtime.get("expires_at"))
|
|
1127
|
+
attestation = build_runtime_attestation(
|
|
1128
|
+
tenant_id=str(session["tenant_id"]),
|
|
1129
|
+
agent_id=str(agent["agent_id"]),
|
|
1130
|
+
runtime_id=str(runtime["runtime_id"]),
|
|
1131
|
+
descriptor_digest=str(descriptor["descriptor_digest"]),
|
|
1132
|
+
runtime_profile=registration_profile["runtime_profile"],
|
|
1133
|
+
guard=guard,
|
|
1134
|
+
created_at=created_at,
|
|
1135
|
+
expires_at=expires_at,
|
|
1136
|
+
)
|
|
1137
|
+
client.submit_runtime_attestation(
|
|
1138
|
+
session,
|
|
1139
|
+
agent,
|
|
1140
|
+
guard,
|
|
1141
|
+
runtime,
|
|
1142
|
+
attestation,
|
|
1143
|
+
idempotency_key=str(uuid.uuid4()),
|
|
1144
|
+
)
|
|
1097
1145
|
run_key = str(uuid.uuid4())
|
|
1098
1146
|
run = client.create_developer_run(
|
|
1099
1147
|
session,
|
|
@@ -1104,6 +1152,7 @@ def run_agent(args: Namespace) -> int:
|
|
|
1104
1152
|
idempotency_key=run_key,
|
|
1105
1153
|
)
|
|
1106
1154
|
persisted: dict[str, Any] = {}
|
|
1155
|
+
preexchange_batch: dict[str, Any] = {}
|
|
1107
1156
|
persisted_spawn: dict[str, Any] = {}
|
|
1108
1157
|
spawn_runtime = (
|
|
1109
1158
|
_subagent_runtime(
|
|
@@ -1184,6 +1233,34 @@ def run_agent(args: Namespace) -> int:
|
|
|
1184
1233
|
descriptor, envelope["action"], envelope["resource"]
|
|
1185
1234
|
)
|
|
1186
1235
|
try:
|
|
1236
|
+
if not preexchange_batch:
|
|
1237
|
+
occurred_at = (
|
|
1238
|
+
datetime.now(UTC)
|
|
1239
|
+
.replace(microsecond=0)
|
|
1240
|
+
.isoformat()
|
|
1241
|
+
.replace("+00:00", "Z")
|
|
1242
|
+
)
|
|
1243
|
+
preexchange_batch.update(
|
|
1244
|
+
build_preexchange_evidence(
|
|
1245
|
+
attestation=attestation,
|
|
1246
|
+
guard=guard,
|
|
1247
|
+
run_id=str(run["runId"]),
|
|
1248
|
+
root_action_id=str(run["rootId"]),
|
|
1249
|
+
action=str(envelope["action"]),
|
|
1250
|
+
resource=str(envelope["resource"]),
|
|
1251
|
+
payload=envelope["payload"],
|
|
1252
|
+
descriptor_digest=str(descriptor["descriptor_digest"]),
|
|
1253
|
+
occurred_at=occurred_at,
|
|
1254
|
+
)
|
|
1255
|
+
)
|
|
1256
|
+
client.submit_runtime_evidence(
|
|
1257
|
+
session,
|
|
1258
|
+
agent,
|
|
1259
|
+
guard,
|
|
1260
|
+
runtime,
|
|
1261
|
+
preexchange_batch,
|
|
1262
|
+
idempotency_key=str(preexchange_batch["idempotencyKey"]),
|
|
1263
|
+
)
|
|
1187
1264
|
action = client.create_developer_action(
|
|
1188
1265
|
session,
|
|
1189
1266
|
agent,
|
|
@@ -1842,6 +1842,115 @@ class DeveloperClient:
|
|
|
1842
1842
|
},
|
|
1843
1843
|
)
|
|
1844
1844
|
|
|
1845
|
+
def submit_runtime_attestation(
|
|
1846
|
+
self,
|
|
1847
|
+
session: dict[str, Any],
|
|
1848
|
+
agent: dict[str, Any],
|
|
1849
|
+
guard: dict[str, Any],
|
|
1850
|
+
runtime: dict[str, Any],
|
|
1851
|
+
attestation: dict[str, Any],
|
|
1852
|
+
*,
|
|
1853
|
+
idempotency_key: str,
|
|
1854
|
+
) -> dict[str, Any]:
|
|
1855
|
+
path = "/v1/developer/runtime-attestations"
|
|
1856
|
+
body = canonical_json(attestation)
|
|
1857
|
+
proof = self.mounted_proof(
|
|
1858
|
+
_decode_private_key(guard.get("private_key")),
|
|
1859
|
+
mode="runtime_proof",
|
|
1860
|
+
tenant_id=_require_string(session.get("tenant_id"), "tenant ID"),
|
|
1861
|
+
agent_id=_require_string(agent.get("agent_id"), "agent ID"),
|
|
1862
|
+
agent_generation=_require_positive_int(
|
|
1863
|
+
int(agent.get("agent_generation", "0")), "agent generation"
|
|
1864
|
+
),
|
|
1865
|
+
method="POST",
|
|
1866
|
+
path=path,
|
|
1867
|
+
body=body,
|
|
1868
|
+
runtime_id=_require_string(runtime.get("runtime_id"), "runtime ID"),
|
|
1869
|
+
)
|
|
1870
|
+
response = self._request(
|
|
1871
|
+
"POST",
|
|
1872
|
+
path,
|
|
1873
|
+
body=body,
|
|
1874
|
+
headers={
|
|
1875
|
+
"X-Palonexus-Developer-Proof": proof,
|
|
1876
|
+
"Idempotency-Key": idempotency_key,
|
|
1877
|
+
},
|
|
1878
|
+
expected={200, 201},
|
|
1879
|
+
allowed_fields={
|
|
1880
|
+
"attestationId",
|
|
1881
|
+
"runtimeSessionId",
|
|
1882
|
+
"manifestHash",
|
|
1883
|
+
"verificationState",
|
|
1884
|
+
"duplicate",
|
|
1885
|
+
},
|
|
1886
|
+
)
|
|
1887
|
+
if (
|
|
1888
|
+
response.get("attestationId") != attestation.get("attestationId")
|
|
1889
|
+
or response.get("runtimeSessionId") != runtime.get("runtime_id")
|
|
1890
|
+
or response.get("manifestHash") != attestation.get("manifestHash")
|
|
1891
|
+
or response.get("verificationState") != "verified"
|
|
1892
|
+
or type(response.get("duplicate")) is not bool
|
|
1893
|
+
):
|
|
1894
|
+
raise ProtocolError("runtime attestation acknowledgement is not bound")
|
|
1895
|
+
return response
|
|
1896
|
+
|
|
1897
|
+
def submit_runtime_evidence(
|
|
1898
|
+
self,
|
|
1899
|
+
session: dict[str, Any],
|
|
1900
|
+
agent: dict[str, Any],
|
|
1901
|
+
guard: dict[str, Any],
|
|
1902
|
+
runtime: dict[str, Any],
|
|
1903
|
+
batch: dict[str, Any],
|
|
1904
|
+
*,
|
|
1905
|
+
idempotency_key: str,
|
|
1906
|
+
) -> dict[str, Any]:
|
|
1907
|
+
path = "/v1/developer/runtime-evidence"
|
|
1908
|
+
body = canonical_json(batch)
|
|
1909
|
+
proof = self.mounted_proof(
|
|
1910
|
+
_decode_private_key(guard.get("private_key")),
|
|
1911
|
+
mode="runtime_proof",
|
|
1912
|
+
tenant_id=_require_string(session.get("tenant_id"), "tenant ID"),
|
|
1913
|
+
agent_id=_require_string(agent.get("agent_id"), "agent ID"),
|
|
1914
|
+
agent_generation=_require_positive_int(
|
|
1915
|
+
int(agent.get("agent_generation", "0")), "agent generation"
|
|
1916
|
+
),
|
|
1917
|
+
method="POST",
|
|
1918
|
+
path=path,
|
|
1919
|
+
body=body,
|
|
1920
|
+
runtime_id=_require_string(runtime.get("runtime_id"), "runtime ID"),
|
|
1921
|
+
)
|
|
1922
|
+
response = self._request(
|
|
1923
|
+
"POST",
|
|
1924
|
+
path,
|
|
1925
|
+
body=body,
|
|
1926
|
+
headers={
|
|
1927
|
+
"X-Palonexus-Developer-Proof": proof,
|
|
1928
|
+
"Idempotency-Key": idempotency_key,
|
|
1929
|
+
},
|
|
1930
|
+
expected={200, 201},
|
|
1931
|
+
allowed_fields={
|
|
1932
|
+
"batchId",
|
|
1933
|
+
"receiptCount",
|
|
1934
|
+
"finalReceiptHash",
|
|
1935
|
+
"achievedLevel",
|
|
1936
|
+
"deliveryState",
|
|
1937
|
+
"duplicate",
|
|
1938
|
+
},
|
|
1939
|
+
)
|
|
1940
|
+
receipts = batch.get("receipts")
|
|
1941
|
+
if (
|
|
1942
|
+
not isinstance(receipts, list)
|
|
1943
|
+
or not receipts
|
|
1944
|
+
or response.get("batchId") != batch.get("batchId")
|
|
1945
|
+
or response.get("receiptCount") != len(receipts)
|
|
1946
|
+
or response.get("finalReceiptHash") != receipts[-1].get("receiptHash")
|
|
1947
|
+
or type(response.get("duplicate")) is not bool
|
|
1948
|
+
or not isinstance(response.get("achievedLevel"), str)
|
|
1949
|
+
or not isinstance(response.get("deliveryState"), str)
|
|
1950
|
+
):
|
|
1951
|
+
raise ProtocolError("runtime evidence acknowledgement is not bound")
|
|
1952
|
+
return response
|
|
1953
|
+
|
|
1845
1954
|
def create_developer_run(
|
|
1846
1955
|
self,
|
|
1847
1956
|
session: dict[str, Any],
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Signed runtime assurance evidence for guarded developer runs."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import base64
|
|
7
|
+
import hashlib
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
import secrets
|
|
11
|
+
import uuid
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import rfc8785
|
|
15
|
+
from cryptography.hazmat.primitives import serialization
|
|
16
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
17
|
+
|
|
18
|
+
VERSION = "palonexus.harness.assurance/v1"
|
|
19
|
+
_HEX_DIGEST = re.compile(r"^[0-9a-f]{64}$")
|
|
20
|
+
_UTC_SECOND = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$")
|
|
21
|
+
_LAYERS = (
|
|
22
|
+
("identity.session", "harness-identity-session", "harness_adapter"),
|
|
23
|
+
("run.ownership", "harness-run-ownership", "harness_adapter"),
|
|
24
|
+
("tool.discovery", "pnxs-tool-discovery", "harness_adapter"),
|
|
25
|
+
("tool.invocation", "pnxs-tool-invocation", "harness_adapter"),
|
|
26
|
+
("mcp.access", "runtime-guard-mcp-access", "runtime_guard"),
|
|
27
|
+
("policy.decision", "control-plane-authz", "control_plane"),
|
|
28
|
+
("authority.delegation", "agent-idp-exact-delegation", "agent_idp_sts"),
|
|
29
|
+
("credential.exchange", "agent-idp-sts-exchange", "agent_idp_sts"),
|
|
30
|
+
("target.enforcement", "registered-target-verifier", "registered_target"),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _canonical(value: Any) -> bytes:
|
|
35
|
+
try:
|
|
36
|
+
return rfc8785.dumps(value)
|
|
37
|
+
except (TypeError, ValueError) as error:
|
|
38
|
+
raise ValueError("runtime evidence is not canonical JSON") from error
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _digest(value: Any) -> str:
|
|
42
|
+
return "sha256:" + hashlib.sha256(_canonical(value)).hexdigest()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _b64url(value: bytes) -> str:
|
|
46
|
+
return base64.urlsafe_b64encode(value).rstrip(b"=").decode("ascii")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _decode(value: object, *, field: str) -> bytes:
|
|
50
|
+
if not isinstance(value, str) or not value:
|
|
51
|
+
raise ValueError(f"invalid {field}")
|
|
52
|
+
try:
|
|
53
|
+
decoded = base64.urlsafe_b64decode(value + "=" * (-len(value) % 4))
|
|
54
|
+
except (ValueError, TypeError) as error:
|
|
55
|
+
raise ValueError(f"invalid {field}") from error
|
|
56
|
+
if len(decoded) != 32 or _b64url(decoded) != value:
|
|
57
|
+
raise ValueError(f"invalid {field}")
|
|
58
|
+
return decoded
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _public_x(value: object, *, field: str) -> str:
|
|
62
|
+
if not isinstance(value, str):
|
|
63
|
+
raise ValueError(f"invalid {field}")
|
|
64
|
+
try:
|
|
65
|
+
parsed = json.loads(value)
|
|
66
|
+
except (json.JSONDecodeError, TypeError) as error:
|
|
67
|
+
raise ValueError(f"invalid {field}") from error
|
|
68
|
+
if not isinstance(parsed, dict) or set(parsed) != {"crv", "kty", "x"}:
|
|
69
|
+
raise ValueError(f"invalid {field}")
|
|
70
|
+
if parsed["crv"] != "Ed25519" or parsed["kty"] != "OKP":
|
|
71
|
+
raise ValueError(f"invalid {field}")
|
|
72
|
+
x = parsed["x"]
|
|
73
|
+
if not isinstance(x, str):
|
|
74
|
+
raise ValueError(f"invalid {field}")
|
|
75
|
+
_decode(x, field=field)
|
|
76
|
+
return x
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _new_credential() -> dict[str, str]:
|
|
80
|
+
private = Ed25519PrivateKey.generate()
|
|
81
|
+
private_raw = private.private_bytes(
|
|
82
|
+
serialization.Encoding.Raw,
|
|
83
|
+
serialization.PrivateFormat.Raw,
|
|
84
|
+
serialization.NoEncryption(),
|
|
85
|
+
)
|
|
86
|
+
public_raw = private.public_key().public_bytes(
|
|
87
|
+
serialization.Encoding.Raw, serialization.PublicFormat.Raw
|
|
88
|
+
)
|
|
89
|
+
public = {"crv": "Ed25519", "kty": "OKP", "x": _b64url(public_raw)}
|
|
90
|
+
return {
|
|
91
|
+
"private_key": _b64url(private_raw),
|
|
92
|
+
"public_key_jwk": _canonical(public).decode("utf-8"),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def ensure_runtime_evidence_credential(guard: dict[str, str]) -> dict[str, str]:
|
|
97
|
+
"""Return a guard credential with a distinct harness-receipt signing key."""
|
|
98
|
+
|
|
99
|
+
value = dict(guard)
|
|
100
|
+
_decode(value.get("private_key"), field="guard private key")
|
|
101
|
+
_public_x(value.get("public_key_jwk"), field="guard public key")
|
|
102
|
+
private = value.get("harness_receipt_private_key")
|
|
103
|
+
public = value.get("harness_receipt_public_key_jwk")
|
|
104
|
+
if private is None and public is None:
|
|
105
|
+
receipt = _new_credential()
|
|
106
|
+
value["harness_receipt_private_key"] = receipt["private_key"]
|
|
107
|
+
value["harness_receipt_public_key_jwk"] = receipt["public_key_jwk"]
|
|
108
|
+
elif private is None or public is None:
|
|
109
|
+
raise ValueError("incomplete harness receipt credential")
|
|
110
|
+
_decode(value.get("harness_receipt_private_key"), field="receipt private key")
|
|
111
|
+
_public_x(value.get("harness_receipt_public_key_jwk"), field="receipt public key")
|
|
112
|
+
if value["public_key_jwk"] == value["harness_receipt_public_key_jwk"]:
|
|
113
|
+
raise ValueError("runtime guard and harness receipt keys must be distinct")
|
|
114
|
+
return value
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _key_id(prefix: str, x: str) -> str:
|
|
118
|
+
return prefix + hashlib.sha256(x.encode("ascii")).hexdigest()[:32]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _jwk(x: str, key_id: str) -> dict[str, str]:
|
|
122
|
+
return {
|
|
123
|
+
"kty": "OKP",
|
|
124
|
+
"crv": "Ed25519",
|
|
125
|
+
"x": x,
|
|
126
|
+
"kid": key_id,
|
|
127
|
+
"use": "sig",
|
|
128
|
+
"alg": "EdDSA",
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _binding(
|
|
133
|
+
*, x: str, key_id: str, signer_class: str, purpose: str, token: bool
|
|
134
|
+
) -> dict[str, Any]:
|
|
135
|
+
return {
|
|
136
|
+
"keyId": key_id,
|
|
137
|
+
"algorithm": "Ed25519",
|
|
138
|
+
"publicKeyJwk": _jwk(x, key_id),
|
|
139
|
+
"signerClass": signer_class,
|
|
140
|
+
"purpose": purpose,
|
|
141
|
+
"grantsAuthority": False,
|
|
142
|
+
"tokenRequestPermitted": token,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _require_digest(value: object, field: str) -> str:
|
|
147
|
+
if not isinstance(value, str) or _HEX_DIGEST.fullmatch(value) is None:
|
|
148
|
+
raise ValueError(f"invalid {field}")
|
|
149
|
+
return value
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _require_time(value: str, field: str) -> str:
|
|
153
|
+
if _UTC_SECOND.fullmatch(value) is None:
|
|
154
|
+
raise ValueError(f"invalid {field}")
|
|
155
|
+
return value
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def build_runtime_attestation(
|
|
159
|
+
*,
|
|
160
|
+
tenant_id: str,
|
|
161
|
+
agent_id: str,
|
|
162
|
+
runtime_id: str,
|
|
163
|
+
descriptor_digest: str,
|
|
164
|
+
runtime_profile: dict[str, Any],
|
|
165
|
+
guard: dict[str, str],
|
|
166
|
+
created_at: str,
|
|
167
|
+
expires_at: str,
|
|
168
|
+
evidence_id: str | None = None,
|
|
169
|
+
manifest_id: str | None = None,
|
|
170
|
+
freshness_nonce: str | None = None,
|
|
171
|
+
) -> dict[str, Any]:
|
|
172
|
+
guard = ensure_runtime_evidence_credential(guard)
|
|
173
|
+
descriptor_digest = _require_digest(descriptor_digest, "descriptor digest")
|
|
174
|
+
profile_digest = _require_digest(
|
|
175
|
+
runtime_profile.get("digest"), "runtime profile digest"
|
|
176
|
+
)
|
|
177
|
+
profile_id = runtime_profile.get("id") or runtime_profile.get("kind")
|
|
178
|
+
if not all(
|
|
179
|
+
isinstance(item, str) and item
|
|
180
|
+
for item in (tenant_id, agent_id, runtime_id, profile_id)
|
|
181
|
+
):
|
|
182
|
+
raise ValueError("incomplete runtime evidence identity")
|
|
183
|
+
if (
|
|
184
|
+
type(runtime_profile.get("version")) is not int
|
|
185
|
+
or runtime_profile["version"] < 1
|
|
186
|
+
):
|
|
187
|
+
raise ValueError("invalid runtime profile version")
|
|
188
|
+
created_at = _require_time(created_at, "created time")
|
|
189
|
+
expires_at = _require_time(expires_at, "expiry time")
|
|
190
|
+
evidence_id = evidence_id or secrets.token_hex(32)
|
|
191
|
+
if _HEX_DIGEST.fullmatch(evidence_id) is None:
|
|
192
|
+
raise ValueError("invalid runtime evidence ID")
|
|
193
|
+
manifest_id = manifest_id or str(uuid.uuid4())
|
|
194
|
+
freshness_nonce = freshness_nonce or secrets.token_hex(16)
|
|
195
|
+
if not manifest_id or not freshness_nonce:
|
|
196
|
+
raise ValueError("invalid runtime evidence nonce")
|
|
197
|
+
|
|
198
|
+
guard_x = _public_x(guard["public_key_jwk"], field="guard public key")
|
|
199
|
+
receipt_x = _public_x(
|
|
200
|
+
guard["harness_receipt_public_key_jwk"], field="receipt public key"
|
|
201
|
+
)
|
|
202
|
+
guard_key_id = _key_id("pnxs-runtime-guard-", guard_x)
|
|
203
|
+
receipt_key_id = _key_id("pnxs-harness-receipt-", receipt_x)
|
|
204
|
+
required = [
|
|
205
|
+
{
|
|
206
|
+
"order": order,
|
|
207
|
+
"layerId": layer,
|
|
208
|
+
"adapterId": adapter,
|
|
209
|
+
"signerClass": signer,
|
|
210
|
+
"requiredWhen": "always",
|
|
211
|
+
}
|
|
212
|
+
for order, (layer, adapter, signer) in enumerate(_LAYERS, start=1)
|
|
213
|
+
]
|
|
214
|
+
adapter_version = "1.0.0"
|
|
215
|
+
enforcement = [
|
|
216
|
+
{
|
|
217
|
+
"layerId": layer,
|
|
218
|
+
"adapterId": adapter,
|
|
219
|
+
"version": adapter_version,
|
|
220
|
+
"digest": _digest(
|
|
221
|
+
{
|
|
222
|
+
"purpose": "palonexus.runtime-enforcement-adapter.v1",
|
|
223
|
+
"descriptorDigest": descriptor_digest,
|
|
224
|
+
"runtimeProfileDigest": profile_digest,
|
|
225
|
+
"layerId": layer,
|
|
226
|
+
"adapterId": adapter,
|
|
227
|
+
"signerClass": signer,
|
|
228
|
+
}
|
|
229
|
+
),
|
|
230
|
+
"signerClass": signer,
|
|
231
|
+
}
|
|
232
|
+
for layer, adapter, signer in _LAYERS
|
|
233
|
+
]
|
|
234
|
+
framework = {
|
|
235
|
+
"layerId": "framework.lifecycle",
|
|
236
|
+
"adapterId": "pnxs-plain-python",
|
|
237
|
+
"version": adapter_version,
|
|
238
|
+
"digest": _digest(
|
|
239
|
+
{
|
|
240
|
+
"purpose": "palonexus.runtime-framework-adapter.v1",
|
|
241
|
+
"descriptorDigest": descriptor_digest,
|
|
242
|
+
"runtimeProfileDigest": profile_digest,
|
|
243
|
+
}
|
|
244
|
+
),
|
|
245
|
+
"signerClass": "harness_adapter",
|
|
246
|
+
}
|
|
247
|
+
manifest = {
|
|
248
|
+
"version": VERSION,
|
|
249
|
+
"manifestId": manifest_id,
|
|
250
|
+
"tenantId": tenant_id,
|
|
251
|
+
"agentId": agent_id,
|
|
252
|
+
"profileId": profile_id,
|
|
253
|
+
"runtimeSessionId": runtime_id,
|
|
254
|
+
"harnessPackageDigest": "sha256:" + descriptor_digest,
|
|
255
|
+
# A plain-Python runtime has no container. The registered runtime-profile
|
|
256
|
+
# digest is the immutable runtime environment identity for this field.
|
|
257
|
+
"containerImageDigest": "sha256:" + profile_digest,
|
|
258
|
+
"frameworkAdapter": framework,
|
|
259
|
+
"enforcementAdapters": enforcement,
|
|
260
|
+
"requiredReceipts": required,
|
|
261
|
+
"requiredLayerManifestHash": _digest(required),
|
|
262
|
+
"harnessReceiptKey": _binding(
|
|
263
|
+
x=receipt_x,
|
|
264
|
+
key_id=receipt_key_id,
|
|
265
|
+
signer_class="harness_adapter",
|
|
266
|
+
purpose="evidence_receipt",
|
|
267
|
+
token=False,
|
|
268
|
+
),
|
|
269
|
+
"guardWorkloadKey": _binding(
|
|
270
|
+
x=guard_x,
|
|
271
|
+
key_id=guard_key_id,
|
|
272
|
+
signer_class="runtime_guard",
|
|
273
|
+
purpose="workload_proof_and_evidence",
|
|
274
|
+
token=True,
|
|
275
|
+
),
|
|
276
|
+
"proofConfirmationMethod": "ed25519-jwk-thumbprint",
|
|
277
|
+
"createdAt": created_at,
|
|
278
|
+
"expiresAt": expires_at,
|
|
279
|
+
"freshnessNonce": freshness_nonce,
|
|
280
|
+
"workloadIdentityEvidenceDigest": _digest(
|
|
281
|
+
{
|
|
282
|
+
"tenantId": tenant_id,
|
|
283
|
+
"agentId": agent_id,
|
|
284
|
+
"runtimeSessionId": runtime_id,
|
|
285
|
+
"guardPublicKeyJwk": _jwk(guard_x, guard_key_id),
|
|
286
|
+
}
|
|
287
|
+
),
|
|
288
|
+
}
|
|
289
|
+
unsigned = {
|
|
290
|
+
"version": VERSION,
|
|
291
|
+
"attestationId": "runtime-guard:" + evidence_id,
|
|
292
|
+
"manifest": manifest,
|
|
293
|
+
"manifestHash": _digest(manifest),
|
|
294
|
+
"attestedAt": created_at,
|
|
295
|
+
"signerKeyId": guard_key_id,
|
|
296
|
+
"signatureAlgorithm": "Ed25519",
|
|
297
|
+
}
|
|
298
|
+
private = Ed25519PrivateKey.from_private_bytes(
|
|
299
|
+
_decode(guard["private_key"], field="guard private key")
|
|
300
|
+
)
|
|
301
|
+
return {**unsigned, "signature": _b64url(private.sign(_canonical(unsigned)))}
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def build_preexchange_evidence(
|
|
305
|
+
*,
|
|
306
|
+
attestation: dict[str, Any],
|
|
307
|
+
guard: dict[str, str],
|
|
308
|
+
run_id: str,
|
|
309
|
+
root_action_id: str,
|
|
310
|
+
action: str,
|
|
311
|
+
resource: str,
|
|
312
|
+
payload: dict[str, Any],
|
|
313
|
+
descriptor_digest: str,
|
|
314
|
+
occurred_at: str,
|
|
315
|
+
batch_id: str | None = None,
|
|
316
|
+
idempotency_key: str | None = None,
|
|
317
|
+
) -> dict[str, Any]:
|
|
318
|
+
guard = ensure_runtime_evidence_credential(guard)
|
|
319
|
+
descriptor_digest = _require_digest(descriptor_digest, "descriptor digest")
|
|
320
|
+
occurred_at = _require_time(occurred_at, "receipt time")
|
|
321
|
+
if not all(
|
|
322
|
+
isinstance(item, str) and item
|
|
323
|
+
for item in (run_id, root_action_id, action, resource)
|
|
324
|
+
):
|
|
325
|
+
raise ValueError("incomplete pre-exchange evidence context")
|
|
326
|
+
if not isinstance(payload, dict):
|
|
327
|
+
raise ValueError("invalid action payload")
|
|
328
|
+
manifest = attestation.get("manifest")
|
|
329
|
+
if not isinstance(manifest, dict) or attestation.get("manifestHash") != _digest(
|
|
330
|
+
manifest
|
|
331
|
+
):
|
|
332
|
+
raise ValueError("invalid runtime attestation")
|
|
333
|
+
required = manifest.get("requiredReceipts")
|
|
334
|
+
if not isinstance(required, list) or [
|
|
335
|
+
(item.get("layerId"), item.get("adapterId"), item.get("signerClass"))
|
|
336
|
+
for item in required[:5]
|
|
337
|
+
] != list(_LAYERS[:5]):
|
|
338
|
+
raise ValueError("runtime manifest does not carry the pre-exchange contract")
|
|
339
|
+
|
|
340
|
+
receipt_private = Ed25519PrivateKey.from_private_bytes(
|
|
341
|
+
_decode(guard["harness_receipt_private_key"], field="receipt private key")
|
|
342
|
+
)
|
|
343
|
+
guard_private = Ed25519PrivateKey.from_private_bytes(
|
|
344
|
+
_decode(guard["private_key"], field="guard private key")
|
|
345
|
+
)
|
|
346
|
+
base_context = {
|
|
347
|
+
"tenantId": manifest["tenantId"],
|
|
348
|
+
"agentId": manifest["agentId"],
|
|
349
|
+
"runtimeSessionId": manifest["runtimeSessionId"],
|
|
350
|
+
"runId": run_id,
|
|
351
|
+
"rootActionId": root_action_id,
|
|
352
|
+
"canonicalAction": action,
|
|
353
|
+
"resource": resource,
|
|
354
|
+
"payloadDigest": _digest(payload),
|
|
355
|
+
"descriptorDigest": descriptor_digest,
|
|
356
|
+
}
|
|
357
|
+
reasons = {
|
|
358
|
+
"identity.session": "runtime_identity_observed",
|
|
359
|
+
"run.ownership": "runtime_run_ownership_observed",
|
|
360
|
+
"tool.discovery": "declared_tool_discovered",
|
|
361
|
+
"tool.invocation": "guarded_tool_invocation_observed",
|
|
362
|
+
"mcp.access": "runtime_guard_mcp_access_observed",
|
|
363
|
+
}
|
|
364
|
+
receipts: list[dict[str, Any]] = []
|
|
365
|
+
prior: str | None = None
|
|
366
|
+
for sequence, (layer, adapter, signer_class) in enumerate(_LAYERS[:5], start=1):
|
|
367
|
+
binding_name = (
|
|
368
|
+
"guardWorkloadKey"
|
|
369
|
+
if signer_class == "runtime_guard"
|
|
370
|
+
else "harnessReceiptKey"
|
|
371
|
+
)
|
|
372
|
+
private = guard_private if signer_class == "runtime_guard" else receipt_private
|
|
373
|
+
unsigned = {
|
|
374
|
+
"version": VERSION,
|
|
375
|
+
"receiptId": str(uuid.uuid4()),
|
|
376
|
+
"tenantId": manifest["tenantId"],
|
|
377
|
+
"agentId": manifest["agentId"],
|
|
378
|
+
"runId": run_id,
|
|
379
|
+
"taskId": root_action_id,
|
|
380
|
+
"rootActionId": root_action_id,
|
|
381
|
+
"attestationId": attestation["attestationId"],
|
|
382
|
+
"manifestHash": attestation["manifestHash"],
|
|
383
|
+
"layerId": layer,
|
|
384
|
+
"adapterId": adapter,
|
|
385
|
+
"signerClass": signer_class,
|
|
386
|
+
"policyVersion": "pnxs-runtime-assurance/v1",
|
|
387
|
+
"catalogVersion": "1",
|
|
388
|
+
"decision": "not_applicable",
|
|
389
|
+
"priorReceiptHash": prior,
|
|
390
|
+
"inputDigest": _digest(
|
|
391
|
+
{**base_context, "layerId": layer, "phase": "input"}
|
|
392
|
+
),
|
|
393
|
+
"outputDigest": _digest(
|
|
394
|
+
{**base_context, "layerId": layer, "outcome": "observed"}
|
|
395
|
+
),
|
|
396
|
+
"outcome": "observed",
|
|
397
|
+
"reasonCode": reasons[layer],
|
|
398
|
+
"sequence": sequence,
|
|
399
|
+
"occurredAt": occurred_at,
|
|
400
|
+
"signerKeyId": manifest[binding_name]["keyId"],
|
|
401
|
+
"signatureAlgorithm": "Ed25519",
|
|
402
|
+
}
|
|
403
|
+
signed = {**unsigned, "signature": _b64url(private.sign(_canonical(unsigned)))}
|
|
404
|
+
receipt = {**signed, "receiptHash": _digest(signed)}
|
|
405
|
+
receipts.append(receipt)
|
|
406
|
+
prior = receipt["receiptHash"]
|
|
407
|
+
return {
|
|
408
|
+
"version": VERSION,
|
|
409
|
+
"batchId": batch_id or str(uuid.uuid4()),
|
|
410
|
+
"tenantId": manifest["tenantId"],
|
|
411
|
+
"submitterSignerClass": "runtime_guard",
|
|
412
|
+
"receipts": receipts,
|
|
413
|
+
"submittedAt": occurred_at,
|
|
414
|
+
"idempotencyKey": idempotency_key or str(uuid.uuid4()),
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
__all__ = [
|
|
419
|
+
"build_preexchange_evidence",
|
|
420
|
+
"build_runtime_attestation",
|
|
421
|
+
"ensure_runtime_evidence_credential",
|
|
422
|
+
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{palonexus-0.3.2 → palonexus-0.3.3}/src/palonexus/developer/templates/plain_python/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|