@datalayer/agent-runtimes 1.5.35 → 1.5.37

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.
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env python3
2
+ # Copyright (c) 2025-2026 Datalayer, Inc.
3
+ # Distributed under the terms of the Modified BSD License.
4
+
5
+ """
6
+ Generate Python and TypeScript code from the YAML policy specifications.
7
+
8
+ One YAML per policy under ``agentspecs/policies`` (SHIELD.md SH-50, §6.7):
9
+ the Shield's policy document under the catalogue's fields. The document's
10
+ schema is the ``agent-shield`` package's, carried by agentspecs verbatim
11
+ (``policies/shield-policy.schema.json``); each document is validated against
12
+ it here before it is written out.
13
+
14
+ Usage:
15
+ python generate_policies.py \\
16
+ --specs-dir agentspecs/agentspecs/policies \\
17
+ --python-output agent_runtimes/specs/policies.py \\
18
+ --typescript-output src/specs/policies.ts
19
+ """
20
+
21
+ import argparse
22
+ import json
23
+ from pathlib import Path
24
+ from typing import Any
25
+
26
+ import jsonschema
27
+ import yaml
28
+ from versioning import ensure_spec_version, version_suffix
29
+
30
+ SHIELD_SCHEMA = "shield-policy.schema.json"
31
+
32
+
33
+ def load_policy_specs(specs_dir: Path) -> list[dict[str, Any]]:
34
+ """Every policy spec of a directory, each named for its id, its document valid."""
35
+ validator = jsonschema.Draft202012Validator(
36
+ json.loads((specs_dir / SHIELD_SCHEMA).read_text())
37
+ )
38
+ specs = []
39
+ for yaml_file in sorted(specs_dir.glob("*.yaml")):
40
+ spec = yaml.safe_load(yaml_file.read_text()) or {}
41
+ ensure_spec_version(spec)
42
+ if spec["id"] != yaml_file.stem:
43
+ raise ValueError(
44
+ f"{yaml_file.name}: the file is named for {yaml_file.stem!r}, the spec says {spec['id']!r}"
45
+ )
46
+ problems = sorted(
47
+ validator.iter_errors(spec.get("policy")),
48
+ key=lambda e: list(e.absolute_path),
49
+ )
50
+ if problems:
51
+ where = (
52
+ ".".join(str(part) for part in problems[0].absolute_path)
53
+ or "the document"
54
+ )
55
+ raise ValueError(
56
+ f"{yaml_file.name}: policy: {where}: {problems[0].message}"
57
+ )
58
+ specs.append(spec)
59
+ return specs
60
+
61
+
62
+ def _const(spec: dict[str, Any]) -> str:
63
+ return (
64
+ spec["id"].replace("-", "_").upper()
65
+ + "_POLICY"
66
+ + version_suffix(spec["version"])
67
+ )
68
+
69
+
70
+ def _s(value: Any) -> str:
71
+ return json.dumps(" ".join(str(value or "").split()), ensure_ascii=False)
72
+
73
+
74
+ def generate_python_code(specs: list[dict[str, Any]]) -> str:
75
+ lines = [
76
+ "# Copyright (c) 2025-2026 Datalayer, Inc.",
77
+ "# Distributed under the terms of the Modified BSD License.",
78
+ '"""',
79
+ "Policy Catalog.",
80
+ "",
81
+ "The Shield's policy documents the platform ships or shows (SHIELD.md SH-50):",
82
+ "each a catalogue entry whose ``policy`` is a document of ``agent_shield.policy``.",
83
+ "",
84
+ "This file is AUTO-GENERATED from YAML specifications.",
85
+ "DO NOT EDIT MANUALLY - run 'make specs' to regenerate.",
86
+ '"""',
87
+ "",
88
+ "from typing import Any, Dict, List, Optional",
89
+ "",
90
+ "from agent_runtimes.types import PolicySpec",
91
+ ]
92
+ for spec in specs:
93
+ lines += [
94
+ "",
95
+ f"{_const(spec)} = PolicySpec(",
96
+ f" id={_s(spec['id'])},",
97
+ f" version={_s(spec['version'])},",
98
+ f" name={_s(spec['name'])},",
99
+ f" description={_s(spec.get('description'))},",
100
+ f" enabled={bool(spec.get('enabled', True))},",
101
+ f" tags={json.dumps(list(spec.get('tags') or []))},",
102
+ f" policy={json.dumps(spec['policy'], ensure_ascii=False, indent=8).replace('true', 'True').replace('false', 'False').replace('null', 'None')},",
103
+ ")",
104
+ ]
105
+ lines += [
106
+ "",
107
+ "",
108
+ "POLICY_CATALOGUE: Dict[str, PolicySpec] = {",
109
+ *[f" {_s(spec['id'])}: {_const(spec)}," for spec in specs],
110
+ "}",
111
+ "",
112
+ "",
113
+ "def get_policy(policy_id: str) -> Optional[PolicySpec]:",
114
+ ' """A policy by id, or None."""',
115
+ " return POLICY_CATALOGUE.get(policy_id)",
116
+ "",
117
+ "",
118
+ "def list_policies() -> List[PolicySpec]:",
119
+ ' """Every policy of the catalogue."""',
120
+ " return list(POLICY_CATALOGUE.values())",
121
+ "",
122
+ "",
123
+ "def policy_documents() -> Dict[str, Dict[str, Any]]:",
124
+ ' """Each policy\'s Shield document, by id."""',
125
+ " return {key: dict(spec.policy) for key, spec in POLICY_CATALOGUE.items()}",
126
+ "",
127
+ ]
128
+ return "\n".join(lines)
129
+
130
+
131
+ def _t(value: Any) -> str:
132
+ return json.dumps(" ".join(str(value or "").split()), ensure_ascii=False)
133
+
134
+
135
+ def _ts_value(value: Any, indent: int = 2) -> str:
136
+ text = json.dumps(value, ensure_ascii=False, indent=2)
137
+ pad = " " * indent
138
+ return "\n".join(
139
+ pad + line if i else line for i, line in enumerate(text.splitlines())
140
+ )
141
+
142
+
143
+ def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
144
+ lines = [
145
+ "/*",
146
+ " * Copyright (c) 2025-2026 Datalayer, Inc.",
147
+ " * Distributed under the terms of the Modified BSD License.",
148
+ " */",
149
+ "",
150
+ "/**",
151
+ " * Policy Catalog.",
152
+ " *",
153
+ " * The Shield's policy documents the platform ships or shows (SHIELD.md SH-50).",
154
+ " *",
155
+ " * This file is AUTO-GENERATED from YAML specifications.",
156
+ " * DO NOT EDIT MANUALLY - run 'make specs' to regenerate.",
157
+ " */",
158
+ "",
159
+ "import type { PolicySpec } from '../types/agentspecs';",
160
+ ]
161
+ for spec in specs:
162
+ lines += [
163
+ "",
164
+ f"export const {_const(spec)}: PolicySpec = {{",
165
+ f" id: {_t(spec['id'])},",
166
+ f" version: {_t(spec['version'])},",
167
+ f" name: {_t(spec['name'])},",
168
+ f" description: {_t(spec.get('description'))},",
169
+ f" enabled: {'true' if spec.get('enabled', True) else 'false'},",
170
+ f" tags: {json.dumps(list(spec.get('tags') or []))},",
171
+ f" policy: {_ts_value(spec['policy'])},",
172
+ "};",
173
+ ]
174
+ lines += [
175
+ "",
176
+ "export const POLICY_CATALOGUE: Record<string, PolicySpec> = {",
177
+ *[f" {_t(spec['id'])}: {_const(spec)}," for spec in specs],
178
+ "};",
179
+ "",
180
+ "/** A policy by id. */",
181
+ "export function getPolicy(policyId: string): PolicySpec | undefined {",
182
+ " return POLICY_CATALOGUE[policyId];",
183
+ "}",
184
+ "",
185
+ "/** Every policy of the catalogue. */",
186
+ "export function listPolicySpecs(): PolicySpec[] {",
187
+ " return Object.values(POLICY_CATALOGUE);",
188
+ "}",
189
+ "",
190
+ ]
191
+ return "\n".join(lines)
192
+
193
+
194
+ def main() -> None:
195
+ parser = argparse.ArgumentParser(description=__doc__)
196
+ parser.add_argument("--specs-dir", type=Path, required=True)
197
+ parser.add_argument("--python-output", type=Path, required=True)
198
+ parser.add_argument("--typescript-output", type=Path, required=True)
199
+ args = parser.parse_args()
200
+ specs = load_policy_specs(args.specs_dir)
201
+ args.python_output.write_text(generate_python_code(specs))
202
+ args.typescript_output.write_text(generate_typescript_code(specs))
203
+ print(
204
+ f"Generated {len(specs)} policies: {args.python_output}, {args.typescript_output}"
205
+ )
206
+
207
+
208
+ if __name__ == "__main__":
209
+ main()