@audiofool/moondog 0.1.0

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.
Files changed (109) hide show
  1. package/README.md +125 -0
  2. package/assets/brand/moondog-lunar-record/moondog-logo-1x1.png +0 -0
  3. package/contracts/v1/capability-policy.schema.json +572 -0
  4. package/contracts/v1/definitions.schema.json +365 -0
  5. package/contracts/v1/library-track-observation.schema.json +138 -0
  6. package/contracts/v1/listening-event.schema.json +111 -0
  7. package/contracts/v1/profile-evidence.schema.json +237 -0
  8. package/contracts/v1/taste-event.schema.json +258 -0
  9. package/contracts/v1/tool-invocation.schema.json +318 -0
  10. package/contracts/v1/tool-receipt.schema.json +1066 -0
  11. package/contracts/v1/track-ref.schema.json +275 -0
  12. package/contracts/v2/profile-evidence.schema.json +284 -0
  13. package/package.json +105 -0
  14. package/scripts/contract-lib.mjs +361 -0
  15. package/scripts/contract-semantics.mjs +2630 -0
  16. package/scripts/demo-studio.mjs +58 -0
  17. package/scripts/moondog +52 -0
  18. package/scripts/moondog.mjs +954 -0
  19. package/src/core/apple-data-migration.mjs +50 -0
  20. package/src/core/apple-library-source-status.mjs +220 -0
  21. package/src/core/apple-projection-domain-services.mjs +2356 -0
  22. package/src/core/capability-catalog.mjs +577 -0
  23. package/src/core/listening-profile-domain-services.mjs +1010 -0
  24. package/src/core/lyric-library.mjs +121 -0
  25. package/src/core/lyric-profile.mjs +50 -0
  26. package/src/core/lyric-service.mjs +41 -0
  27. package/src/core/moondog-application.mjs +1827 -0
  28. package/src/core/state-directory.mjs +19 -0
  29. package/src/core/synthetic-domain-services.mjs +1485 -0
  30. package/src/core/uuid-v5.mjs +42 -0
  31. package/src/demo/fictional-spotify-history.mjs +400 -0
  32. package/src/demo/moondog-demo.mjs +355 -0
  33. package/src/demo/moondog-interactive-tasteprint-demo.mjs +293 -0
  34. package/src/demo/moondog-tasteprint-demo.mjs +631 -0
  35. package/src/importers/apple-music-library/import-batch.mjs +290 -0
  36. package/src/importers/apple-music-library/index.mjs +76 -0
  37. package/src/importers/apple-music-library/normalize.mjs +1126 -0
  38. package/src/importers/apple-music-library/parse-plist.mjs +357 -0
  39. package/src/importers/apple-music-library/projection-records.mjs +557 -0
  40. package/src/importers/apple-music-library/read-batch.mjs +276 -0
  41. package/src/importers/apple-music-library/sqlite-projection.mjs +1307 -0
  42. package/src/importers/apple-music-library/stable-ids.mjs +19 -0
  43. package/src/importers/apple-music-library/track-snapshot.schema.json +136 -0
  44. package/src/integrations/apple-music/catalog.mjs +770 -0
  45. package/src/integrations/cross-catalog-artist-identity.mjs +222 -0
  46. package/src/integrations/listenbrainz/history-file.mjs +560 -0
  47. package/src/integrations/lyrics/lrclib.mjs +55 -0
  48. package/src/integrations/open-music-similarity/artist-radio.mjs +806 -0
  49. package/src/integrations/public-playlists.mjs +104 -0
  50. package/src/integrations/spotify/account-data-history.mjs +545 -0
  51. package/src/integrations/spotify/account-data-profile.mjs +918 -0
  52. package/src/integrations/spotify/authentication.mjs +791 -0
  53. package/src/integrations/spotify/catalog-resolver.mjs +657 -0
  54. package/src/integrations/spotify/connection.mjs +125 -0
  55. package/src/integrations/spotify/credential-store.mjs +26 -0
  56. package/src/integrations/spotify/extended-streaming-history.mjs +525 -0
  57. package/src/integrations/spotify/history-archive.mjs +59 -0
  58. package/src/integrations/spotify/index.mjs +80 -0
  59. package/src/integrations/spotify/recent-activity.mjs +198 -0
  60. package/src/integrations/spotify/resolution-cache.mjs +260 -0
  61. package/src/integrations/spotify/service.mjs +713 -0
  62. package/src/integrations/spotify/settings.mjs +89 -0
  63. package/src/integrations/spotify/web-api-client.mjs +761 -0
  64. package/src/integrations/web/codex-web.mjs +263 -0
  65. package/src/integrations/youtube-music/takeout.mjs +143 -0
  66. package/src/memory/local-memory-store.mjs +1705 -0
  67. package/src/memory/reflection-worker.mjs +87 -0
  68. package/src/profile/apple-library-import.mjs +28 -0
  69. package/src/profile/history-import.mjs +163 -0
  70. package/src/profile/listener-corrections.mjs +217 -0
  71. package/src/profile/listening-history-store.mjs +2055 -0
  72. package/src/profile/listening-profile-projection.mjs +2520 -0
  73. package/src/profile/local-music-data.mjs +812 -0
  74. package/src/profile/music-import-bundle.mjs +91 -0
  75. package/src/profile/music-providers.mjs +25 -0
  76. package/src/profile/spotify-archive-catalog-hints.mjs +210 -0
  77. package/src/profile/spotify-archive-taste.mjs +482 -0
  78. package/src/runtime/pi/agent-runtime.mjs +6444 -0
  79. package/src/runtime/pi/authentication.mjs +104 -0
  80. package/src/runtime/pi/configured-runtime.mjs +98 -0
  81. package/src/runtime/pi/memory-agent-runtime.mjs +337 -0
  82. package/src/runtime/pi/model-catalog.mjs +62 -0
  83. package/src/runtime/pi/model-transport.mjs +117 -0
  84. package/src/runtime/pi/persistent-credential-store.mjs +670 -0
  85. package/src/runtime/pi/provider-registry.mjs +74 -0
  86. package/src/runtime/pi/runtime-settings.mjs +81 -0
  87. package/src/surfaces/cli/auth-command.mjs +355 -0
  88. package/src/surfaces/cli/brand-components.mjs +390 -0
  89. package/src/surfaces/cli/brand-theme.mjs +231 -0
  90. package/src/surfaces/cli/catalog-command.mjs +509 -0
  91. package/src/surfaces/cli/command-completions.mjs +113 -0
  92. package/src/surfaces/cli/data-command.mjs +214 -0
  93. package/src/surfaces/cli/format-output.mjs +750 -0
  94. package/src/surfaces/cli/history-import-view.mjs +445 -0
  95. package/src/surfaces/cli/import-guides.mjs +9 -0
  96. package/src/surfaces/cli/listenbrainz-command.mjs +129 -0
  97. package/src/surfaces/cli/listening-menu.mjs +171 -0
  98. package/src/surfaces/cli/profile-command.mjs +290 -0
  99. package/src/surfaces/cli/spotify-command.mjs +849 -0
  100. package/src/surfaces/cli/taste-profile-model.mjs +306 -0
  101. package/src/surfaces/cli/taste-profile-view.mjs +272 -0
  102. package/src/surfaces/cli/terminal-art.mjs +295 -0
  103. package/src/surfaces/cli/tui.mjs +1714 -0
  104. package/src/surfaces/cli/web-command.mjs +52 -0
  105. package/src/surfaces/html/tasteprint-artifact.mjs +96 -0
  106. package/src/surfaces/html/tasteprint.mjs +3575 -0
  107. package/src/surfaces/web/showcase.mjs +272 -0
  108. package/src/surfaces/web/studio-page.mjs +4148 -0
  109. package/src/surfaces/web/studio.mjs +2288 -0
package/README.md ADDED
@@ -0,0 +1,125 @@
1
+ <p align="center">
2
+ <img src="assets/brand/moondog-lunar-record/moondog-logo-1x1.png" width="144" alt="Moondog lunar record logo">
3
+ </p>
4
+
5
+ <h1 align="center">Moondog</h1>
6
+
7
+ <p align="center"><strong>A personal music agent that shows its work.</strong></p>
8
+
9
+ <p align="center">Your listening history. Your say in what it means.</p>
10
+
11
+ <p align="center">
12
+ <img src="assets/demo/moondog-tui-home.png" width="1000" alt="Moondog's terminal listening room: a lunar record drawn in characters, a short menu, a Pink Floyd lyric, and a message box">
13
+ </p>
14
+
15
+ <p align="center"><sub>The real terminal app, shown with fictional listening data.</sub></p>
16
+
17
+ Moondog reads the history your music services already keep and turns it into a profile you can open, question, and correct.
18
+ It tells you what it found, shows the evidence behind every reading, and is honest about what play counts can never know.
19
+ When you want company, an agent can use that profile to talk music, find songs you haven't heard, and shape a playlist with you.
20
+
21
+ It runs in your terminal, keeps your data on your machine, and is built on [Pi](https://github.com/earendil-works/pi).
22
+
23
+ ## Start listening
24
+
25
+ You need Node.js **22.19.0 or newer** on macOS or Linux.
26
+
27
+ ```bash
28
+ git clone https://github.com/Audiofool934/moondog.git
29
+ cd moondog
30
+ npm ci
31
+ npm start
32
+ ```
33
+
34
+ Your profile works without a model, an API key, or a music-service login.
35
+ To see the agent at work with fictional data and no sign-in at all, run `npm run demo`.
36
+
37
+ ## Bring your history
38
+
39
+ Type `/import` and pick **Spotify**, **Apple Music**, **YouTube Music**, **QQ Music**, or **NetEase Cloud Music**.
40
+ Moondog walks you through getting your data, then shows you what's inside before anything is added.
41
+
42
+ - **Spotify:** your latest plays in a minute, or your whole history from the ZIP Spotify emails you.
43
+ - **Apple Music:** the `Library.xml` you export from Music on a Mac.
44
+ - **YouTube Music:** a Google Takeout export.
45
+ - **QQ Music and NetEase:** any public playlist, from its share link.
46
+ - **ListenBrainz:** a saved listens file.
47
+
48
+ A Spotify ZIP can also go straight in:
49
+
50
+ ```text
51
+ /import "/path/to/spotify-history.zip"
52
+ ```
53
+
54
+ When the import finishes, your profile opens right there in the same TUI session, with the full report kept in the conversation above it.
55
+ Importing the same file twice changes nothing, and your original files are never touched.
56
+ Some sources aren't supported yet, such as Apple's privacy download and full QQ or NetEase listening history; the import guide says so where it matters.
57
+ The [terminal guide](docs/TERMINAL_GUIDE.md#import-and-inspect-listening-history) covers every format in detail.
58
+
59
+ ## See what it says, and tell it what it got wrong
60
+
61
+ | In your profile | What you can do |
62
+ | --- | --- |
63
+ | **Find a song or artist** | Type to filter, then open the one you want. |
64
+ | **See why it's there** | Read what you played, what that can't prove, and what you told Moondog, each on its own. |
65
+ | **Make a choice** | Choose **I like this** (Like) or **Keep it out** (Avoid), and watch the profile update. |
66
+ | **Change your mind** | **Undo my choice** at any time; you can retract anything, and the original listening history never changes. |
67
+ | **Find more like this** | Start an editable request for three songs, seeded from the track you picked. |
68
+
69
+ <p align="center">
70
+ <img src="assets/demo/moondog-tui-profile.png" width="1000" alt="Moondog's listening profile showing the fictional track Midnight Lines, why it is there, and what that evidence cannot tell">
71
+ </p>
72
+
73
+ <p align="center"><sub>The profile with fictional data. Playing a song a lot shows you know it, not that you love it.</sub></p>
74
+
75
+ Type `/taste report` for the whole picture on one page.
76
+ It walks through your years one song at a time, finds songs that went quiet and songs that came back, and ends with the dark side of the moon: what your listening data can't tell anyone.
77
+ You can also save it as a private web page that works offline.
78
+
79
+ ## Getting around
80
+
81
+ | Command or key | What it does |
82
+ | --- | --- |
83
+ | `/taste` | Open your profile. |
84
+ | Type, ↑ ↓, Enter | Filter, pick, and see why something is there, or make a choice. |
85
+ | Tab in the profile | Switch between All, Tracks, Artists, and Your choices. |
86
+ | Esc | Go back; whatever you were typing stays put. |
87
+ | Ctrl+P | Search every command. |
88
+ | `/resume` · `/new` | Pick up a saved conversation, or start a fresh one. |
89
+ | `/home` | Back to the record sleeve, without clearing the conversation. |
90
+
91
+ Each launch starts a new conversation, while your profile and anything Moondog remembers carry over.
92
+ Change the look with `/theme paper` or `/theme charcoal`, switch the artwork with `/art ascii`, or keep it still with `/motion off`.
93
+
94
+ ## Add an agent when you want one
95
+
96
+ Sign in with `/auth` to GLM, Kimi, DeepSeek, Grok, GPT, Claude, Gemini, or OpenRouter, then pick a model with `/model`.
97
+ `openai-codex` uses your ChatGPT sign-in.
98
+ You can switch models mid-conversation; the [model setup guide](docs/TERMINAL_GUIDE.md#enable-agent-conversation) lists provider IDs and environment variables.
99
+
100
+ Ask for a route through your own history, reshuffle what it suggests, or reach past your library.
101
+ Every song the agent picks has to come from a real list it looked up, and what it read about the wider music world stays separate from what it knows about you.
102
+
103
+ | Optional connection | What it adds |
104
+ | --- | --- |
105
+ | **A model through Pi** | Conversation, discovery, and building playlists together. |
106
+ | **Codex CLI** | `/web` lookups with links to their sources, using your own signed-in Codex CLI. |
107
+ | **Spotify sign-in** | Playback control, and private playlists made only when you ask. |
108
+
109
+ Your profile never touches the network.
110
+ The agent and connected features talk to the services you set up; `/web` sends only the question or link you give it, never your profile, history, or files.
111
+ Importing a Spotify ZIP doesn't need a Spotify sign-in.
112
+ Read the [connection and data details](docs/TERMINAL_GUIDE.md) before you connect anything.
113
+
114
+ ## Explore further
115
+
116
+ - [Terminal guide](docs/TERMINAL_GUIDE.md): import formats, commands, appearance, models, Spotify, memory, and your local data.
117
+ - [Private Tasteprint](docs/PRIVATE_TASTEPRINT.md): what the evidence can support, and what a report reveals about you.
118
+ - [Product charter](docs/PRODUCT_CHARTER.md) and [public roadmap](docs/ROADMAP.md): where Moondog is headed, and what it won't do.
119
+ - [Voice](docs/VOICE.md): how Moondog talks, and where Pink Floyd shows up.
120
+ - [Runtime architecture](docs/ADR_0001_AGENT_RUNTIME_AND_CLI.md) and [data contracts](contracts/README.md): how the pieces fit together.
121
+ - [Contribution guide](CONTRIBUTING.md) and [release readiness](docs/PUBLIC_RELEASE_READINESS.md): local setup, checks, and how private data is kept out.
122
+
123
+ Moondog is early, and the Pi-based TUI is where the work happens.
124
+ GUI and Studio development is paused; the earlier [browser prototypes](docs/TERMINAL_GUIDE.md#archived-gui-prototypes-paused) are still there for reference.
125
+ There is no open-source license yet.
@@ -0,0 +1,572 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "urn:moondog:contract:capability-policy:1",
4
+ "title": "Moondog CapabilityPolicy v1",
5
+ "type": "object",
6
+ "properties": {
7
+ "schema_version": {
8
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/schema_version"
9
+ },
10
+ "policy_id": {
11
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/uuid"
12
+ },
13
+ "policy_version": {
14
+ "type": "integer",
15
+ "minimum": 1
16
+ },
17
+ "subject_id": {
18
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/uuid"
19
+ },
20
+ "effect": {
21
+ "enum": [
22
+ "allow",
23
+ "deny"
24
+ ]
25
+ },
26
+ "enabled": {
27
+ "type": "boolean"
28
+ },
29
+ "capability_ids": {
30
+ "type": "array",
31
+ "minItems": 1,
32
+ "items": {
33
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/namespaced_key"
34
+ },
35
+ "uniqueItems": true
36
+ },
37
+ "actor_types": {
38
+ "type": "array",
39
+ "minItems": 1,
40
+ "items": {
41
+ "enum": [
42
+ "user",
43
+ "agent",
44
+ "service"
45
+ ]
46
+ },
47
+ "uniqueItems": true
48
+ },
49
+ "actor_refs": {
50
+ "type": "array",
51
+ "minItems": 1,
52
+ "items": {
53
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/actor_ref"
54
+ },
55
+ "uniqueItems": true
56
+ },
57
+ "surfaces": {
58
+ "type": "array",
59
+ "minItems": 1,
60
+ "items": {
61
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/namespaced_key"
62
+ },
63
+ "uniqueItems": true
64
+ },
65
+ "account_refs": {
66
+ "type": "array",
67
+ "minItems": 1,
68
+ "items": {
69
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/external_ref"
70
+ },
71
+ "uniqueItems": true
72
+ },
73
+ "allowed_execution_modes": {
74
+ "type": "array",
75
+ "minItems": 1,
76
+ "items": {
77
+ "enum": [
78
+ "plan",
79
+ "dry_run",
80
+ "execute"
81
+ ]
82
+ },
83
+ "uniqueItems": true
84
+ },
85
+ "allowed_effects": {
86
+ "type": "array",
87
+ "minItems": 1,
88
+ "items": {
89
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/effect_kind"
90
+ },
91
+ "uniqueItems": true
92
+ },
93
+ "risk_limits": {
94
+ "type": "array",
95
+ "minItems": 1,
96
+ "items": {
97
+ "type": "object",
98
+ "properties": {
99
+ "domain": {
100
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/risk_domain"
101
+ },
102
+ "max_level": {
103
+ "enum": [
104
+ "none",
105
+ "low",
106
+ "medium",
107
+ "high"
108
+ ]
109
+ }
110
+ },
111
+ "required": [
112
+ "domain",
113
+ "max_level"
114
+ ],
115
+ "additionalProperties": false
116
+ }
117
+ },
118
+ "privacy": {
119
+ "type": "object",
120
+ "properties": {
121
+ "purpose_id": {
122
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/namespaced_key"
123
+ },
124
+ "purpose": {
125
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/non_empty_string"
126
+ },
127
+ "allowed_data_classes": {
128
+ "type": "array",
129
+ "minItems": 1,
130
+ "items": {
131
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/data_class"
132
+ },
133
+ "uniqueItems": true
134
+ },
135
+ "allowed_destinations": {
136
+ "type": "array",
137
+ "minItems": 1,
138
+ "items": {
139
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/external_destination"
140
+ },
141
+ "uniqueItems": true
142
+ },
143
+ "raw_data_egress_allowed": {
144
+ "type": "boolean"
145
+ },
146
+ "rights_basis_required": {
147
+ "type": "boolean"
148
+ },
149
+ "retention_days": {
150
+ "type": "integer",
151
+ "minimum": 0
152
+ }
153
+ },
154
+ "required": [
155
+ "purpose_id",
156
+ "purpose",
157
+ "allowed_data_classes",
158
+ "allowed_destinations",
159
+ "raw_data_egress_allowed",
160
+ "rights_basis_required"
161
+ ],
162
+ "additionalProperties": false
163
+ },
164
+ "confirmation": {
165
+ "type": "object",
166
+ "properties": {
167
+ "mode": {
168
+ "enum": [
169
+ "never",
170
+ "always"
171
+ ]
172
+ },
173
+ "scope": {
174
+ "const": "invocation"
175
+ },
176
+ "ttl_seconds": {
177
+ "type": "integer",
178
+ "minimum": 1,
179
+ "maximum": 3600
180
+ },
181
+ "max_uses": {
182
+ "type": "integer",
183
+ "const": 1
184
+ }
185
+ },
186
+ "required": [
187
+ "mode"
188
+ ],
189
+ "allOf": [
190
+ {
191
+ "if": {
192
+ "properties": {
193
+ "mode": {
194
+ "const": "never"
195
+ }
196
+ },
197
+ "required": [
198
+ "mode"
199
+ ]
200
+ },
201
+ "then": {
202
+ "not": {
203
+ "anyOf": [
204
+ {
205
+ "required": [
206
+ "scope"
207
+ ]
208
+ },
209
+ {
210
+ "required": [
211
+ "ttl_seconds"
212
+ ]
213
+ },
214
+ {
215
+ "required": [
216
+ "max_uses"
217
+ ]
218
+ }
219
+ ]
220
+ }
221
+ },
222
+ "else": {
223
+ "required": [
224
+ "scope",
225
+ "ttl_seconds",
226
+ "max_uses"
227
+ ]
228
+ }
229
+ }
230
+ ],
231
+ "additionalProperties": false
232
+ },
233
+ "budgets": {
234
+ "type": "array",
235
+ "items": {
236
+ "type": "object",
237
+ "properties": {
238
+ "resource": {
239
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/namespaced_key"
240
+ },
241
+ "unit": {
242
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/non_empty_string"
243
+ },
244
+ "max_per_invocation": {
245
+ "type": "integer",
246
+ "minimum": 0
247
+ },
248
+ "max_per_window": {
249
+ "type": "integer",
250
+ "minimum": 0
251
+ },
252
+ "window_seconds": {
253
+ "type": "integer",
254
+ "minimum": 1
255
+ }
256
+ },
257
+ "required": [
258
+ "resource",
259
+ "unit"
260
+ ],
261
+ "anyOf": [
262
+ {
263
+ "required": [
264
+ "max_per_invocation"
265
+ ]
266
+ },
267
+ {
268
+ "required": [
269
+ "max_per_window",
270
+ "window_seconds"
271
+ ]
272
+ }
273
+ ],
274
+ "dependentRequired": {
275
+ "max_per_window": [
276
+ "window_seconds"
277
+ ],
278
+ "window_seconds": [
279
+ "max_per_window"
280
+ ]
281
+ },
282
+ "additionalProperties": false
283
+ }
284
+ },
285
+ "idempotency": {
286
+ "type": "object",
287
+ "properties": {
288
+ "required_for": {
289
+ "type": "array",
290
+ "items": {
291
+ "enum": [
292
+ "write_local",
293
+ "write_external",
294
+ "send_message",
295
+ "spend",
296
+ "publish",
297
+ "delete"
298
+ ]
299
+ },
300
+ "uniqueItems": true
301
+ },
302
+ "key_ttl_seconds": {
303
+ "type": "integer",
304
+ "minimum": 86400
305
+ }
306
+ },
307
+ "required": [
308
+ "required_for",
309
+ "key_ttl_seconds"
310
+ ],
311
+ "additionalProperties": false
312
+ },
313
+ "retry": {
314
+ "type": "object",
315
+ "properties": {
316
+ "max_attempts": {
317
+ "type": "integer",
318
+ "minimum": 1,
319
+ "maximum": 10
320
+ },
321
+ "backoff": {
322
+ "enum": [
323
+ "none",
324
+ "fixed",
325
+ "exponential"
326
+ ]
327
+ },
328
+ "base_delay_ms": {
329
+ "type": "integer",
330
+ "minimum": 0
331
+ },
332
+ "retryable_categories": {
333
+ "type": "array",
334
+ "items": {
335
+ "enum": [
336
+ "timeout",
337
+ "rate_limit",
338
+ "external_service"
339
+ ]
340
+ },
341
+ "uniqueItems": true
342
+ }
343
+ },
344
+ "required": [
345
+ "max_attempts",
346
+ "backoff",
347
+ "base_delay_ms",
348
+ "retryable_categories"
349
+ ],
350
+ "additionalProperties": false
351
+ },
352
+ "timeout_seconds": {
353
+ "type": "integer",
354
+ "minimum": 1,
355
+ "maximum": 3600
356
+ },
357
+ "escalate_on": {
358
+ "type": "array",
359
+ "items": {
360
+ "enum": [
361
+ "approval_expired",
362
+ "budget_exceeded",
363
+ "authentication_required",
364
+ "human_challenge",
365
+ "required_context_missing",
366
+ "repeated_failure",
367
+ "ambiguous_intent",
368
+ "irreversible_action",
369
+ "unknown_outcome"
370
+ ]
371
+ },
372
+ "uniqueItems": true
373
+ },
374
+ "audit": {
375
+ "type": "object",
376
+ "properties": {
377
+ "required": {
378
+ "type": "boolean"
379
+ },
380
+ "redact_argument_paths": {
381
+ "type": "array",
382
+ "items": {
383
+ "type": "string",
384
+ "pattern": "^/"
385
+ },
386
+ "uniqueItems": true
387
+ },
388
+ "redact_result_paths": {
389
+ "type": "array",
390
+ "items": {
391
+ "type": "string",
392
+ "pattern": "^/"
393
+ },
394
+ "uniqueItems": true
395
+ },
396
+ "retention_days": {
397
+ "type": "integer",
398
+ "minimum": 0
399
+ }
400
+ },
401
+ "required": [
402
+ "required",
403
+ "redact_argument_paths",
404
+ "redact_result_paths"
405
+ ],
406
+ "additionalProperties": false
407
+ },
408
+ "valid_from": {
409
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/utc_timestamp"
410
+ },
411
+ "valid_until": {
412
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/utc_timestamp"
413
+ },
414
+ "priority": {
415
+ "type": "integer",
416
+ "minimum": 0,
417
+ "maximum": 1000
418
+ },
419
+ "extensions": {
420
+ "$ref": "urn:moondog:contract:definitions:1#/$defs/extensions"
421
+ }
422
+ },
423
+ "required": [
424
+ "schema_version",
425
+ "policy_id",
426
+ "policy_version",
427
+ "subject_id",
428
+ "effect",
429
+ "enabled",
430
+ "capability_ids",
431
+ "actor_types",
432
+ "surfaces",
433
+ "allowed_execution_modes",
434
+ "allowed_effects",
435
+ "risk_limits",
436
+ "privacy",
437
+ "confirmation",
438
+ "budgets",
439
+ "idempotency",
440
+ "retry",
441
+ "timeout_seconds",
442
+ "escalate_on",
443
+ "audit",
444
+ "valid_from"
445
+ ],
446
+ "allOf": [
447
+ {
448
+ "if": {
449
+ "properties": {
450
+ "effect": {
451
+ "const": "allow"
452
+ },
453
+ "allowed_effects": {
454
+ "type": "array",
455
+ "contains": {
456
+ "enum": [
457
+ "write_external",
458
+ "send_message",
459
+ "spend",
460
+ "publish",
461
+ "delete",
462
+ "use_credentials"
463
+ ]
464
+ }
465
+ }
466
+ },
467
+ "required": [
468
+ "effect",
469
+ "allowed_effects"
470
+ ]
471
+ },
472
+ "then": {
473
+ "required": [
474
+ "account_refs"
475
+ ],
476
+ "properties": {
477
+ "confirmation": {
478
+ "type": "object",
479
+ "properties": {
480
+ "mode": {
481
+ "const": "always"
482
+ }
483
+ }
484
+ }
485
+ }
486
+ }
487
+ },
488
+ {
489
+ "if": {
490
+ "properties": {
491
+ "effect": {
492
+ "const": "allow"
493
+ },
494
+ "allowed_effects": {
495
+ "type": "array",
496
+ "contains": {
497
+ "const": "spend"
498
+ }
499
+ }
500
+ },
501
+ "required": [
502
+ "effect",
503
+ "allowed_effects"
504
+ ]
505
+ },
506
+ "then": {
507
+ "properties": {
508
+ "budgets": {
509
+ "type": "array",
510
+ "minItems": 1
511
+ }
512
+ }
513
+ }
514
+ },
515
+ {
516
+ "if": {
517
+ "properties": {
518
+ "effect": {
519
+ "const": "allow"
520
+ }
521
+ },
522
+ "required": [
523
+ "effect"
524
+ ]
525
+ },
526
+ "then": {
527
+ "required": [
528
+ "actor_refs"
529
+ ]
530
+ }
531
+ },
532
+ {
533
+ "if": {
534
+ "properties": {
535
+ "effect": {
536
+ "const": "allow"
537
+ },
538
+ "allowed_effects": {
539
+ "type": "array",
540
+ "contains": {
541
+ "enum": [
542
+ "write_external",
543
+ "send_message",
544
+ "spend",
545
+ "publish",
546
+ "delete",
547
+ "use_credentials"
548
+ ]
549
+ }
550
+ }
551
+ },
552
+ "required": [
553
+ "effect",
554
+ "allowed_effects"
555
+ ]
556
+ },
557
+ "then": {
558
+ "properties": {
559
+ "audit": {
560
+ "type": "object",
561
+ "properties": {
562
+ "required": {
563
+ "const": true
564
+ }
565
+ }
566
+ }
567
+ }
568
+ }
569
+ }
570
+ ],
571
+ "additionalProperties": false
572
+ }