@datalayer/agent-runtimes 1.5.35 → 1.5.36

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,16 @@
1
+ /**
2
+ * Policy Catalog.
3
+ *
4
+ * The Shield's policy documents the platform ships or shows (SHIELD.md SH-50).
5
+ *
6
+ * This file is AUTO-GENERATED from YAML specifications.
7
+ * DO NOT EDIT MANUALLY - run 'make specs' to regenerate.
8
+ */
9
+ import type { PolicySpec } from '../types/agentspecs';
10
+ export declare const DEFAULT_POLICY_0_0_1: PolicySpec;
11
+ export declare const ORGANIZATION_BASELINE_POLICY_0_0_1: PolicySpec;
12
+ export declare const POLICY_CATALOGUE: Record<string, PolicySpec>;
13
+ /** A policy by id. */
14
+ export declare function getPolicy(policyId: string): PolicySpec | undefined;
15
+ /** Every policy of the catalogue. */
16
+ export declare function listPolicySpecs(): PolicySpec[];
@@ -0,0 +1,228 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ export const DEFAULT_POLICY_0_0_1 = {
6
+ id: 'default',
7
+ version: '0.0.1',
8
+ name: 'The default policy',
9
+ description: "Decision 5's default, the session document every Shield decides with under the organization's and the application's: read, search, edit and move inside the workspace are done; delete, run a command, reach outside the workspace, a command naming a credential, sudo, a package install from the network or git push are asked first with the command shown; a fetch goes through the allow-list, which is empty; send and buy are asked.",
10
+ enabled: true,
11
+ tags: ['policy', 'default'],
12
+ policy: {
13
+ scope: 'session',
14
+ version: 1,
15
+ rules: [
16
+ {
17
+ name: 'read inside the workspace',
18
+ decision: 'allow',
19
+ classes: ['read'],
20
+ kinds: [],
21
+ where: 'inside',
22
+ paths: [],
23
+ commands: [],
24
+ },
25
+ {
26
+ name: 'edit and move inside the workspace',
27
+ decision: 'allow',
28
+ classes: ['write'],
29
+ kinds: [],
30
+ where: 'inside',
31
+ paths: [],
32
+ commands: [],
33
+ reason: 'shown as diffs',
34
+ },
35
+ {
36
+ name: 'delete',
37
+ decision: 'ask',
38
+ classes: ['delete'],
39
+ kinds: [],
40
+ where: 'any',
41
+ paths: [],
42
+ commands: [],
43
+ },
44
+ {
45
+ name: 'a command naming a credential',
46
+ decision: 'ask',
47
+ classes: ['execute'],
48
+ kinds: [],
49
+ where: 'any',
50
+ paths: [],
51
+ commands: [
52
+ '(?i)(api[_-]?key|secret|token|password|passwd|credential|\\.env\\b|id_(rsa|ed25519)|\\.ssh/|\\.aws/|\\.netrc|\\.datalayer/|datalayerrc)',
53
+ ],
54
+ reason: 'the command is shown',
55
+ },
56
+ {
57
+ name: 'sudo',
58
+ decision: 'ask',
59
+ classes: ['execute'],
60
+ kinds: [],
61
+ where: 'any',
62
+ paths: [],
63
+ commands: ['(?:^|[\\s;&|(])(?:sudo|doas|su)\\b'],
64
+ reason: 'the command is shown',
65
+ },
66
+ {
67
+ name: 'a package install from the network',
68
+ decision: 'ask',
69
+ classes: ['execute'],
70
+ kinds: [],
71
+ where: 'any',
72
+ paths: [],
73
+ commands: [
74
+ '\\b(?:pip3?|pipx|uv|npm|pnpm|yarn|bun|apt(?:-get)?|brew|cargo|gem|conda|mamba|go)(?:\\s+pip)?\\s+(?:install|add|i|get)\\b',
75
+ ],
76
+ reason: 'the command is shown',
77
+ },
78
+ {
79
+ name: 'git push',
80
+ decision: 'ask',
81
+ classes: ['execute'],
82
+ kinds: [],
83
+ where: 'any',
84
+ paths: [],
85
+ commands: ['\\bgit\\b[^;&|]*\\bpush\\b'],
86
+ reason: 'the command is shown',
87
+ },
88
+ {
89
+ name: 'run a command',
90
+ decision: 'ask',
91
+ classes: ['execute'],
92
+ kinds: [],
93
+ where: 'any',
94
+ paths: [],
95
+ commands: [],
96
+ },
97
+ {
98
+ name: 'outside the workspace',
99
+ decision: 'ask',
100
+ classes: [],
101
+ kinds: [],
102
+ where: 'outside',
103
+ paths: [],
104
+ commands: [],
105
+ },
106
+ {
107
+ name: 'send or buy',
108
+ decision: 'ask',
109
+ classes: ['send', 'buy'],
110
+ kinds: [],
111
+ where: 'any',
112
+ paths: [],
113
+ commands: [],
114
+ },
115
+ ],
116
+ limits: {
117
+ tool_calls: {
118
+ soft: 100,
119
+ hard: 200,
120
+ },
121
+ terminals_open: {
122
+ soft: 4,
123
+ hard: 8,
124
+ },
125
+ bytes_written: {
126
+ soft: 5242880,
127
+ hard: 20971520,
128
+ },
129
+ },
130
+ network: {
131
+ fetch: [],
132
+ },
133
+ mcp: {
134
+ allow: [],
135
+ },
136
+ harnesses: {
137
+ admit: [],
138
+ credentials: 'account',
139
+ },
140
+ },
141
+ };
142
+ export const ORGANIZATION_BASELINE_POLICY_0_0_1 = {
143
+ id: 'organization-baseline',
144
+ version: '0.0.1',
145
+ name: "An organization's baseline",
146
+ description: "An example of the organization scope an administrator writes in IAM (SHIELD.md §6.7): running a command asks, nothing is deleted, nothing outside the workspace; the limits of §6.3 with a model budget of 200 000 then 400 000 tokens and a spend limit of 5 then 10 USD (500 then 1 000 credits); fetches to *.datalayer.io alone; Claude Code and Codex admitted, each on the account's own key.",
147
+ enabled: true,
148
+ tags: ['policy', 'organization', 'example'],
149
+ policy: {
150
+ scope: 'organization',
151
+ version: 1,
152
+ rules: [
153
+ {
154
+ name: 'running a command asks',
155
+ decision: 'ask',
156
+ classes: ['execute'],
157
+ kinds: [],
158
+ where: 'any',
159
+ paths: [],
160
+ commands: [],
161
+ reason: "the organization's rule: every command is shown to a person first",
162
+ },
163
+ {
164
+ name: 'nothing is deleted',
165
+ decision: 'deny',
166
+ classes: ['delete'],
167
+ kinds: [],
168
+ where: 'any',
169
+ paths: [],
170
+ commands: [],
171
+ reason: "the organization's rule",
172
+ },
173
+ {
174
+ name: 'outside the workspace: leave it to me',
175
+ decision: 'deny',
176
+ classes: [],
177
+ kinds: [],
178
+ where: 'outside',
179
+ paths: [],
180
+ commands: [],
181
+ },
182
+ ],
183
+ limits: {
184
+ tool_calls: {
185
+ soft: 100,
186
+ hard: 200,
187
+ },
188
+ terminals_open: {
189
+ soft: 2,
190
+ hard: 4,
191
+ },
192
+ bytes_written: {
193
+ soft: 5242880,
194
+ hard: 20971520,
195
+ },
196
+ model_tokens: {
197
+ soft: 200000,
198
+ hard: 400000,
199
+ },
200
+ spend: {
201
+ soft: 500,
202
+ hard: 1000,
203
+ },
204
+ },
205
+ network: {
206
+ fetch: ['*.datalayer.io'],
207
+ },
208
+ mcp: {
209
+ allow: [],
210
+ },
211
+ harnesses: {
212
+ admit: ['claude-code', 'codex'],
213
+ credentials: 'account',
214
+ },
215
+ },
216
+ };
217
+ export const POLICY_CATALOGUE = {
218
+ default: DEFAULT_POLICY_0_0_1,
219
+ 'organization-baseline': ORGANIZATION_BASELINE_POLICY_0_0_1,
220
+ };
221
+ /** A policy by id. */
222
+ export function getPolicy(policyId) {
223
+ return POLICY_CATALOGUE[policyId];
224
+ }
225
+ /** Every policy of the catalogue. */
226
+ export function listPolicySpecs() {
227
+ return Object.values(POLICY_CATALOGUE);
228
+ }
@@ -226,6 +226,49 @@ export interface HarnessAcpSpec {
226
226
  * one beside `pydantic-ai` and `vercel-ai`; the Datalayer runtime serves it
227
227
  * through its ACP adapter, `ACPAgent` (SHIELD SH-10), never the browser.
228
228
  */
229
+ /**
230
+ * A Shield policy document the platform ships or shows (`agentspecs/policies`,
231
+ * SHIELD.md SH-50): the catalogue's fields around a document of
232
+ * `@datalayer/agent-shield`'s `Policy`, whose schema agentspecs carries verbatim.
233
+ */
234
+ export interface PolicySpec {
235
+ id: string;
236
+ version: string;
237
+ name: string;
238
+ description: string;
239
+ enabled: boolean;
240
+ tags: string[];
241
+ /** The Shield's document: scope, version, rules, limits, network, mcp, harnesses. */
242
+ policy: {
243
+ scope: 'organization' | 'team' | 'session';
244
+ version?: number;
245
+ expires?: string | null;
246
+ rules?: Array<{
247
+ name: string;
248
+ decision: 'allow' | 'ask' | 'deny';
249
+ classes?: string[];
250
+ kinds?: string[];
251
+ where?: 'inside' | 'outside' | 'any';
252
+ paths?: string[];
253
+ commands?: string[];
254
+ reason?: string | null;
255
+ }>;
256
+ limits?: Record<string, {
257
+ soft: number;
258
+ hard: number;
259
+ } | null>;
260
+ network?: {
261
+ fetch?: string[];
262
+ };
263
+ mcp?: {
264
+ allow?: string[];
265
+ };
266
+ harnesses?: {
267
+ admit?: string[];
268
+ credentials?: 'account' | 'platform' | 'either';
269
+ };
270
+ };
271
+ }
229
272
  export interface HarnessSpec {
230
273
  /** What `harness:` names (e.g. 'claude-code'). */
231
274
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/agent-runtimes",
3
- "version": "1.5.35",
3
+ "version": "1.5.36",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",
@@ -1782,6 +1782,7 @@ export * from './protocols';
1782
1782
  export * from './frames';
1783
1783
  export * from './cogs';
1784
1784
  export * from './harnesses';
1785
+ export * from './policies';
1785
1786
  export * from './guards';
1786
1787
  export * from './gates';
1787
1788
  export * from './tracks';
@@ -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()