gravity-schema 0.5.2__tar.gz → 0.5.4__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.
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/PKG-INFO +1 -1
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/gravity_schema/catalog_imported.yaml +478 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/gravity_schema/cron.py +26 -1
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/gravity_schema/manifest.py +56 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/gravity_schema/validation.py +11 -1
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/pyproject.toml +1 -1
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/tests/test_cron.py +16 -1
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/tests/test_manifest.py +106 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/.gitignore +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/LICENSE +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/NOTICE +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/README.md +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/gravity_schema/__init__.py +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/gravity_schema/__main__.py +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/gravity_schema/catalog.py +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/gravity_schema/catalog.yaml +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/gravity_schema/contract.py +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/gravity_schema/errors.py +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/gravity_schema/export.py +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/gravity_schema/gen_catalog.py +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/gravity_schema/source_scan.py +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/schema/manifest.schema.json +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/tests/conftest.py +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/tests/test_catalog_parity.py +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/tests/test_errors.py +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/tests/test_json_schema.py +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/tests/test_kind_outputs.py +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/tests/test_slots.py +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/tests/test_source_scan.py +0 -0
- {gravity_schema-0.5.2 → gravity_schema-0.5.4}/tests/test_validation.py +0 -0
|
@@ -13156,3 +13156,481 @@
|
|
|
13156
13156
|
breadth: generic
|
|
13157
13157
|
guarantee: experimental
|
|
13158
13158
|
side_effect: read
|
|
13159
|
+
- name: whatsapp.configure_conversational_automation
|
|
13160
|
+
slug: WHATSAPP_CONFIGURE_CONVERSATIONAL_AUTOMATION
|
|
13161
|
+
toolkit: whatsapp
|
|
13162
|
+
description: Configure conversational automation settings for a WhatsApp Business phone number. Sets up away messages, greeting messages, custom commands, and ice breaker prompts to automate customer interactions....
|
|
13163
|
+
scopes:
|
|
13164
|
+
- whatsapp_business_messaging
|
|
13165
|
+
- whatsapp_business_management
|
|
13166
|
+
breadth: generic
|
|
13167
|
+
guarantee: experimental
|
|
13168
|
+
side_effect: write
|
|
13169
|
+
- name: whatsapp.create_flow
|
|
13170
|
+
slug: WHATSAPP_CREATE_FLOW
|
|
13171
|
+
toolkit: whatsapp
|
|
13172
|
+
description: Create a new WhatsApp Flow for the WhatsApp Business Account. Use this action when you want to create an interactive flow that can collect user data, guide users through a process, or provide structur...
|
|
13173
|
+
scopes:
|
|
13174
|
+
- whatsapp_business_messaging
|
|
13175
|
+
- whatsapp_business_management
|
|
13176
|
+
breadth: generic
|
|
13177
|
+
guarantee: experimental
|
|
13178
|
+
side_effect: write
|
|
13179
|
+
- name: whatsapp.create_max_price_agreements
|
|
13180
|
+
slug: WHATSAPP_CREATE_MAX_PRICE_AGREEMENTS
|
|
13181
|
+
toolkit: whatsapp
|
|
13182
|
+
description: Create a max price agreement for WhatsApp marketing messages pricing. Max price agreements set a ceiling on the per-conversation price you'll pay for WhatsApp messaging across your WhatsApp Business A...
|
|
13183
|
+
scopes:
|
|
13184
|
+
- business_management
|
|
13185
|
+
- whatsapp_business_management
|
|
13186
|
+
- whatsapp_business_messaging
|
|
13187
|
+
breadth: generic
|
|
13188
|
+
guarantee: experimental
|
|
13189
|
+
side_effect: write
|
|
13190
|
+
- name: whatsapp.create_message_template
|
|
13191
|
+
slug: WHATSAPP_CREATE_MESSAGE_TEMPLATE
|
|
13192
|
+
toolkit: whatsapp
|
|
13193
|
+
description: Create a new message template for the WhatsApp Business Account. Templates must be approved by WhatsApp before they can be used. Templates are required for marketing messages and messages sent outside...
|
|
13194
|
+
scopes:
|
|
13195
|
+
- whatsapp_business_management
|
|
13196
|
+
breadth: generic
|
|
13197
|
+
guarantee: experimental
|
|
13198
|
+
side_effect: write
|
|
13199
|
+
- name: whatsapp.create_qr_code
|
|
13200
|
+
slug: WHATSAPP_CREATE_QR_CODE
|
|
13201
|
+
toolkit: whatsapp
|
|
13202
|
+
description: Create a new QR code with a prefilled message for a WhatsApp Business phone number. Use this action when you need to generate a shareable QR code that customers can scan to start a conversation with y...
|
|
13203
|
+
scopes:
|
|
13204
|
+
- whatsapp_business_management
|
|
13205
|
+
breadth: generic
|
|
13206
|
+
guarantee: experimental
|
|
13207
|
+
side_effect: write
|
|
13208
|
+
- name: whatsapp.create_upload_session
|
|
13209
|
+
slug: WHATSAPP_CREATE_UPLOAD_SESSION
|
|
13210
|
+
toolkit: whatsapp
|
|
13211
|
+
description: Create a resumable upload session for large file uploads to WhatsApp. This action initiates an upload session that allows you to upload large files in chunks. First create a session, then use the sess...
|
|
13212
|
+
scopes:
|
|
13213
|
+
- whatsapp_business_messaging
|
|
13214
|
+
breadth: generic
|
|
13215
|
+
guarantee: experimental
|
|
13216
|
+
side_effect: write
|
|
13217
|
+
- name: whatsapp.get_activities
|
|
13218
|
+
slug: WHATSAPP_GET_ACTIVITIES
|
|
13219
|
+
toolkit: whatsapp
|
|
13220
|
+
description: Retrieve activity history for a WhatsApp Business Account. This action returns a paginated list of activities such as phone number verifications, user additions, and other administrative actions perfo...
|
|
13221
|
+
scopes:
|
|
13222
|
+
- whatsapp_business_management
|
|
13223
|
+
breadth: generic
|
|
13224
|
+
guarantee: experimental
|
|
13225
|
+
side_effect: read
|
|
13226
|
+
- name: whatsapp.get_business_account_details
|
|
13227
|
+
slug: WHATSAPP_GET_BUSINESS_ACCOUNT_DETAILS
|
|
13228
|
+
toolkit: whatsapp
|
|
13229
|
+
description: Retrieve comprehensive details about a WhatsApp Business Account (WABA). Returns configuration, status, and settings information including account review status, business verification status, timezone...
|
|
13230
|
+
scopes:
|
|
13231
|
+
- whatsapp_business_messaging
|
|
13232
|
+
breadth: generic
|
|
13233
|
+
guarantee: experimental
|
|
13234
|
+
side_effect: read
|
|
13235
|
+
- name: whatsapp.get_business_compliance_info
|
|
13236
|
+
slug: WHATSAPP_GET_BUSINESS_COMPLIANCE_INFO
|
|
13237
|
+
toolkit: whatsapp
|
|
13238
|
+
description: Retrieve comprehensive business compliance information for a WhatsApp Business phone number. Returns regulatory and compliance details including entity registration status, grievance officer contact i...
|
|
13239
|
+
scopes:
|
|
13240
|
+
- whatsapp_business_management
|
|
13241
|
+
breadth: generic
|
|
13242
|
+
guarantee: experimental
|
|
13243
|
+
side_effect: read
|
|
13244
|
+
- name: whatsapp.get_business_encryption
|
|
13245
|
+
slug: WHATSAPP_GET_BUSINESS_ENCRYPTION
|
|
13246
|
+
toolkit: whatsapp
|
|
13247
|
+
description: Retrieve the WhatsApp Business public key and its signature verification status. Use this action when you need to obtain the end-to-end encryption public key for a WhatsApp Business phone number. This...
|
|
13248
|
+
scopes:
|
|
13249
|
+
- whatsapp_business_messaging
|
|
13250
|
+
breadth: generic
|
|
13251
|
+
guarantee: experimental
|
|
13252
|
+
side_effect: read
|
|
13253
|
+
- name: whatsapp.get_business_profile
|
|
13254
|
+
slug: WHATSAPP_GET_BUSINESS_PROFILE
|
|
13255
|
+
toolkit: whatsapp
|
|
13256
|
+
description: Get the business profile information for a WhatsApp Business phone number. This includes business details like description, address, website, and contact info.
|
|
13257
|
+
scopes:
|
|
13258
|
+
- whatsapp_business_messaging
|
|
13259
|
+
breadth: generic
|
|
13260
|
+
guarantee: experimental
|
|
13261
|
+
side_effect: read
|
|
13262
|
+
- name: whatsapp.get_client_business_accounts
|
|
13263
|
+
slug: WHATSAPP_GET_CLIENT_BUSINESS_ACCOUNTS
|
|
13264
|
+
toolkit: whatsapp
|
|
13265
|
+
description: Retrieve WhatsApp Business Accounts shared with or managed by a business portfolio. Use this action when you need to get WhatsApp Business Accounts (WABAs) that are client accounts shared with or mana...
|
|
13266
|
+
scopes:
|
|
13267
|
+
- whatsapp_business_management
|
|
13268
|
+
breadth: generic
|
|
13269
|
+
guarantee: experimental
|
|
13270
|
+
side_effect: read
|
|
13271
|
+
- name: whatsapp.get_commerce_settings
|
|
13272
|
+
slug: WHATSAPP_GET_COMMERCE_SETTINGS
|
|
13273
|
+
toolkit: whatsapp
|
|
13274
|
+
description: Get commerce settings for a WhatsApp Business phone number. Retrieves cart and catalog visibility settings for a specific WhatsApp Business phone number. Use this action to check whether shopping cart...
|
|
13275
|
+
scopes:
|
|
13276
|
+
- whatsapp_business_messaging
|
|
13277
|
+
breadth: generic
|
|
13278
|
+
guarantee: experimental
|
|
13279
|
+
side_effect: read
|
|
13280
|
+
- name: whatsapp.get_flow_assets
|
|
13281
|
+
slug: WHATSAPP_GET_FLOW_ASSETS
|
|
13282
|
+
toolkit: whatsapp
|
|
13283
|
+
description: List assets associated with a WhatsApp Flow. Use this action when you need to retrieve the assets (such as flow.json) that are associated with a WhatsApp Flow. The response includes download URLs for...
|
|
13284
|
+
scopes:
|
|
13285
|
+
- whatsapp_business_messaging
|
|
13286
|
+
breadth: generic
|
|
13287
|
+
guarantee: experimental
|
|
13288
|
+
side_effect: read
|
|
13289
|
+
- name: whatsapp.get_join_requests
|
|
13290
|
+
slug: WHATSAPP_GET_JOIN_REQUESTS
|
|
13291
|
+
toolkit: whatsapp
|
|
13292
|
+
description: Get all pending join requests for a WhatsApp group. Use this action when you need to retrieve pending requests from users who want to join a WhatsApp group. This is useful for group administration, mo...
|
|
13293
|
+
scopes:
|
|
13294
|
+
- whatsapp_business_management
|
|
13295
|
+
- whatsapp_business_messaging
|
|
13296
|
+
breadth: generic
|
|
13297
|
+
guarantee: experimental
|
|
13298
|
+
side_effect: read
|
|
13299
|
+
- name: whatsapp.get_media_info
|
|
13300
|
+
slug: WHATSAPP_GET_MEDIA_INFO
|
|
13301
|
+
toolkit: whatsapp
|
|
13302
|
+
description: Get metadata and download URL for uploaded WhatsApp media. Returns media ID, download URL (valid for 5 minutes), MIME type, SHA256 hash, and file size. The download URL can be used to retrieve the act...
|
|
13303
|
+
scopes:
|
|
13304
|
+
- whatsapp_business_messaging
|
|
13305
|
+
breadth: generic
|
|
13306
|
+
guarantee: experimental
|
|
13307
|
+
side_effect: read
|
|
13308
|
+
- name: whatsapp.get_message_history
|
|
13309
|
+
slug: WHATSAPP_GET_MESSAGE_HISTORY
|
|
13310
|
+
toolkit: whatsapp
|
|
13311
|
+
description: Retrieve message history for a WhatsApp Business phone number. Returns a paginated list of messages with their delivery status information, including events like delivery status updates, timestamps, a...
|
|
13312
|
+
scopes:
|
|
13313
|
+
- whatsapp_business_messaging
|
|
13314
|
+
- whatsapp_business_management
|
|
13315
|
+
breadth: generic
|
|
13316
|
+
guarantee: experimental
|
|
13317
|
+
side_effect: read
|
|
13318
|
+
- name: whatsapp.get_message_templates
|
|
13319
|
+
slug: WHATSAPP_GET_MESSAGE_TEMPLATES
|
|
13320
|
+
toolkit: whatsapp
|
|
13321
|
+
description: Get all message templates for the WhatsApp Business Account. Templates are required for sending messages outside the 24-hour window and for marketing/utility messages.
|
|
13322
|
+
scopes:
|
|
13323
|
+
- whatsapp_business_management
|
|
13324
|
+
breadth: generic
|
|
13325
|
+
guarantee: experimental
|
|
13326
|
+
side_effect: read
|
|
13327
|
+
- name: whatsapp.get_owned_business_accounts
|
|
13328
|
+
slug: WHATSAPP_GET_OWNED_BUSINESS_ACCOUNTS
|
|
13329
|
+
toolkit: whatsapp
|
|
13330
|
+
description: Retrieve WhatsApp Business Accounts owned by a business portfolio. Use this action when you need to get all WhatsApp Business Accounts (WABAs) that are directly owned by a specific business. This diff...
|
|
13331
|
+
scopes:
|
|
13332
|
+
- whatsapp_business_messaging
|
|
13333
|
+
breadth: generic
|
|
13334
|
+
guarantee: experimental
|
|
13335
|
+
side_effect: read
|
|
13336
|
+
- name: whatsapp.get_phone_number
|
|
13337
|
+
slug: WHATSAPP_GET_PHONE_NUMBER
|
|
13338
|
+
toolkit: whatsapp
|
|
13339
|
+
description: Retrieve detailed information about a specific WhatsApp Business phone number. Returns phone number details including verification status, quality rating, display number, verified business name, throu...
|
|
13340
|
+
scopes:
|
|
13341
|
+
- whatsapp_business_management
|
|
13342
|
+
breadth: generic
|
|
13343
|
+
guarantee: experimental
|
|
13344
|
+
side_effect: read
|
|
13345
|
+
- name: whatsapp.get_phone_numbers
|
|
13346
|
+
slug: WHATSAPP_GET_PHONE_NUMBERS
|
|
13347
|
+
toolkit: whatsapp
|
|
13348
|
+
description: Retrieve all phone numbers registered to your WhatsApp Business Account. Returns phone number IDs, display numbers, verification status, quality ratings, and messaging throughput limits. Use the phone...
|
|
13349
|
+
scopes:
|
|
13350
|
+
- whatsapp_business_management
|
|
13351
|
+
breadth: generic
|
|
13352
|
+
guarantee: experimental
|
|
13353
|
+
side_effect: read
|
|
13354
|
+
- name: whatsapp.get_schedules
|
|
13355
|
+
slug: WHATSAPP_GET_SCHEDULES
|
|
13356
|
+
toolkit: whatsapp
|
|
13357
|
+
description: Retrieve campaign schedules associated with a WhatsApp Business Account. Use this action when you need to list all scheduled campaigns for a WABA, including their status, timing, and recurrence patter...
|
|
13358
|
+
scopes:
|
|
13359
|
+
- whatsapp_business_management
|
|
13360
|
+
breadth: generic
|
|
13361
|
+
guarantee: experimental
|
|
13362
|
+
side_effect: read
|
|
13363
|
+
- name: whatsapp.get_settings
|
|
13364
|
+
slug: WHATSAPP_GET_SETTINGS
|
|
13365
|
+
toolkit: whatsapp
|
|
13366
|
+
description: Get Cloud API settings for a WhatsApp Business phone number. Retrieves calling, storage, and payload encryption settings for a specific WhatsApp Business phone number. This includes information about...
|
|
13367
|
+
scopes:
|
|
13368
|
+
- whatsapp_business_management
|
|
13369
|
+
- whatsapp_business_messaging
|
|
13370
|
+
breadth: generic
|
|
13371
|
+
guarantee: experimental
|
|
13372
|
+
side_effect: read
|
|
13373
|
+
- name: whatsapp.get_subscribed_apps
|
|
13374
|
+
slug: WHATSAPP_GET_SUBSCRIBED_APPS
|
|
13375
|
+
toolkit: whatsapp
|
|
13376
|
+
description: Get all applications currently subscribed to webhooks for a WhatsApp Business Account. Use this action when you need to retrieve the list of third-party applications or integrations that are subscribe...
|
|
13377
|
+
scopes:
|
|
13378
|
+
- whatsapp_business_management
|
|
13379
|
+
- whatsapp_business_messaging
|
|
13380
|
+
breadth: generic
|
|
13381
|
+
guarantee: experimental
|
|
13382
|
+
side_effect: read
|
|
13383
|
+
- name: whatsapp.get_template_library
|
|
13384
|
+
slug: WHATSAPP_GET_TEMPLATE_LIBRARY
|
|
13385
|
+
toolkit: whatsapp
|
|
13386
|
+
description: Browse the pre-built message template library for WhatsApp Business Account. This retrieves a list of available templates that can be used for messaging. Use when you need to discover available messag...
|
|
13387
|
+
scopes:
|
|
13388
|
+
- whatsapp_business_messaging
|
|
13389
|
+
- whatsapp_business_management
|
|
13390
|
+
breadth: generic
|
|
13391
|
+
guarantee: experimental
|
|
13392
|
+
side_effect: read
|
|
13393
|
+
- name: whatsapp.get_template_status
|
|
13394
|
+
slug: WHATSAPP_GET_TEMPLATE_STATUS
|
|
13395
|
+
toolkit: whatsapp
|
|
13396
|
+
description: Get the status and details of a specific message template. This is useful for checking if a template has been approved, rejected, or is still pending review.
|
|
13397
|
+
scopes:
|
|
13398
|
+
- whatsapp_business_management
|
|
13399
|
+
breadth: generic
|
|
13400
|
+
guarantee: experimental
|
|
13401
|
+
side_effect: read
|
|
13402
|
+
- name: whatsapp.list_flows
|
|
13403
|
+
slug: WHATSAPP_LIST_FLOWS
|
|
13404
|
+
toolkit: whatsapp
|
|
13405
|
+
description: List all WhatsApp Flows associated with the WhatsApp Business Account. Returns flow details including name, status, categories, and any validation errors. Use when you need to retrieve all available f...
|
|
13406
|
+
scopes:
|
|
13407
|
+
- whatsapp_business_management
|
|
13408
|
+
breadth: generic
|
|
13409
|
+
guarantee: experimental
|
|
13410
|
+
side_effect: read
|
|
13411
|
+
- name: whatsapp.list_groups
|
|
13412
|
+
slug: WHATSAPP_LIST_GROUPS
|
|
13413
|
+
toolkit: whatsapp
|
|
13414
|
+
description: List all WhatsApp groups associated with a business phone number. Use this action when you need to retrieve the list of groups that a business phone number is a member of. This is useful for managing...
|
|
13415
|
+
scopes:
|
|
13416
|
+
- whatsapp_business_management
|
|
13417
|
+
- whatsapp_business_messaging
|
|
13418
|
+
breadth: generic
|
|
13419
|
+
guarantee: experimental
|
|
13420
|
+
side_effect: read
|
|
13421
|
+
- name: whatsapp.list_qr_codes
|
|
13422
|
+
slug: WHATSAPP_LIST_QR_CODES
|
|
13423
|
+
toolkit: whatsapp
|
|
13424
|
+
description: Get a list of all QR codes for a WhatsApp Business phone number. Use this action when you need to retrieve all message QR codes associated with a specific WhatsApp Business phone number. This is usefu...
|
|
13425
|
+
scopes:
|
|
13426
|
+
- whatsapp_business_management
|
|
13427
|
+
breadth: generic
|
|
13428
|
+
guarantee: experimental
|
|
13429
|
+
side_effect: read
|
|
13430
|
+
- name: whatsapp.list_solutions
|
|
13431
|
+
slug: WHATSAPP_LIST_SOLUTIONS
|
|
13432
|
+
toolkit: whatsapp
|
|
13433
|
+
description: Retrieve a paginated list of Multi-Partner Solutions for a WhatsApp Business Account. Use when you need to view all multi-partner solutions (such as SMB solutions) associated with a WABA, including th...
|
|
13434
|
+
scopes:
|
|
13435
|
+
- whatsapp_business_management
|
|
13436
|
+
breadth: generic
|
|
13437
|
+
guarantee: experimental
|
|
13438
|
+
side_effect: read
|
|
13439
|
+
- name: whatsapp.publish_flow
|
|
13440
|
+
slug: WHATSAPP_PUBLISH_FLOW
|
|
13441
|
+
toolkit: whatsapp
|
|
13442
|
+
description: Publish a WhatsApp Flow to make it available for use. Use this action when you have finished designing and testing a WhatsApp Flow and are ready to make it available for users. The flow must be in DRA...
|
|
13443
|
+
scopes:
|
|
13444
|
+
- whatsapp_business_management
|
|
13445
|
+
- whatsapp_business_messaging
|
|
13446
|
+
breadth: generic
|
|
13447
|
+
guarantee: experimental
|
|
13448
|
+
side_effect: write
|
|
13449
|
+
- name: whatsapp.register_phone
|
|
13450
|
+
slug: WHATSAPP_REGISTER_PHONE
|
|
13451
|
+
toolkit: whatsapp
|
|
13452
|
+
description: Register a WhatsApp Business phone number for use with WhatsApp Cloud API. Use when you need to register a phone number that has been added to your WhatsApp Business Account. This action requires sett...
|
|
13453
|
+
scopes:
|
|
13454
|
+
- whatsapp_business_management
|
|
13455
|
+
- whatsapp_business_messaging
|
|
13456
|
+
breadth: generic
|
|
13457
|
+
guarantee: experimental
|
|
13458
|
+
side_effect: write
|
|
13459
|
+
- name: whatsapp.send_contacts
|
|
13460
|
+
slug: WHATSAPP_SEND_CONTACTS
|
|
13461
|
+
toolkit: whatsapp
|
|
13462
|
+
description: 'Send contacts WhatsApp number. Note: The message will be delivered to the recipient only if they have initiated a conversation first.'
|
|
13463
|
+
scopes:
|
|
13464
|
+
- whatsapp_business_messaging
|
|
13465
|
+
breadth: generic
|
|
13466
|
+
guarantee: experimental
|
|
13467
|
+
side_effect: write
|
|
13468
|
+
- name: whatsapp.send_interactive_buttons
|
|
13469
|
+
slug: WHATSAPP_SEND_INTERACTIVE_BUTTONS
|
|
13470
|
+
toolkit: whatsapp
|
|
13471
|
+
description: Send an interactive button message with up to 3 reply buttons to a WhatsApp user. Interactive button messages allow recipients to quickly respond by tapping predefined buttons. Perfect for yes/no ques...
|
|
13472
|
+
scopes:
|
|
13473
|
+
- whatsapp_business_messaging
|
|
13474
|
+
breadth: generic
|
|
13475
|
+
guarantee: experimental
|
|
13476
|
+
side_effect: write
|
|
13477
|
+
- name: whatsapp.send_interactive_list
|
|
13478
|
+
slug: WHATSAPP_SEND_INTERACTIVE_LIST
|
|
13479
|
+
toolkit: whatsapp
|
|
13480
|
+
description: Send an interactive list message to a WhatsApp number. List messages display a menu of options organized into sections. Users tap a button to view the list and select one option. Perfect for product c...
|
|
13481
|
+
scopes:
|
|
13482
|
+
- whatsapp_business_messaging
|
|
13483
|
+
breadth: generic
|
|
13484
|
+
guarantee: experimental
|
|
13485
|
+
side_effect: write
|
|
13486
|
+
- name: whatsapp.send_location
|
|
13487
|
+
slug: WHATSAPP_SEND_LOCATION
|
|
13488
|
+
toolkit: whatsapp
|
|
13489
|
+
description: Send a location message with coordinates, name, and address to a WhatsApp user. This action allows you to share location information through WhatsApp Business API. The location message includes latitu...
|
|
13490
|
+
scopes:
|
|
13491
|
+
- whatsapp_business_messaging
|
|
13492
|
+
breadth: generic
|
|
13493
|
+
guarantee: experimental
|
|
13494
|
+
side_effect: write
|
|
13495
|
+
- name: whatsapp.send_media
|
|
13496
|
+
slug: WHATSAPP_SEND_MEDIA
|
|
13497
|
+
toolkit: whatsapp
|
|
13498
|
+
description: 'Send a media message to a WhatsApp number. Note: The media will be delivered to the recipient only if they have texted first.'
|
|
13499
|
+
scopes:
|
|
13500
|
+
- whatsapp_business_messaging
|
|
13501
|
+
breadth: generic
|
|
13502
|
+
guarantee: experimental
|
|
13503
|
+
side_effect: write
|
|
13504
|
+
- name: whatsapp.send_media_by_id
|
|
13505
|
+
slug: WHATSAPP_SEND_MEDIA_BY_ID
|
|
13506
|
+
toolkit: whatsapp
|
|
13507
|
+
description: Send media using a media ID from previously uploaded media. This is more efficient than sending media by URL as the media is already on WhatsApp servers. Use upload_media action first to get the media...
|
|
13508
|
+
scopes:
|
|
13509
|
+
- whatsapp_business_messaging
|
|
13510
|
+
breadth: generic
|
|
13511
|
+
guarantee: experimental
|
|
13512
|
+
side_effect: write
|
|
13513
|
+
- name: whatsapp.send_message
|
|
13514
|
+
slug: WHATSAPP_SEND_MESSAGE
|
|
13515
|
+
toolkit: whatsapp
|
|
13516
|
+
description: 'Send a text message to a WhatsApp user. Important: The recipient phone number must be registered on WhatsApp and must have initiated a conversation with your business within the last 24 hours, OR you...'
|
|
13517
|
+
scopes:
|
|
13518
|
+
- whatsapp_business_messaging
|
|
13519
|
+
breadth: generic
|
|
13520
|
+
guarantee: experimental
|
|
13521
|
+
side_effect: write
|
|
13522
|
+
- name: whatsapp.send_template_message
|
|
13523
|
+
slug: WHATSAPP_SEND_TEMPLATE_MESSAGE
|
|
13524
|
+
toolkit: whatsapp
|
|
13525
|
+
description: Send a template message to a WhatsApp number.
|
|
13526
|
+
scopes:
|
|
13527
|
+
- whatsapp_business_messaging
|
|
13528
|
+
breadth: generic
|
|
13529
|
+
guarantee: experimental
|
|
13530
|
+
side_effect: write
|
|
13531
|
+
- name: whatsapp.set_two_step_verification
|
|
13532
|
+
slug: WHATSAPP_SET_TWO_STEP_VERIFICATION
|
|
13533
|
+
toolkit: whatsapp
|
|
13534
|
+
description: Set or reset the two-step verification PIN for a WhatsApp Business phone number. Use when you need to change the 6-digit PIN that protects a registered phone number — for example after onboarding a ne...
|
|
13535
|
+
scopes:
|
|
13536
|
+
- whatsapp_business_management
|
|
13537
|
+
- whatsapp_business_messaging
|
|
13538
|
+
breadth: generic
|
|
13539
|
+
guarantee: experimental
|
|
13540
|
+
side_effect: write
|
|
13541
|
+
- name: whatsapp.subscribe_app
|
|
13542
|
+
slug: WHATSAPP_SUBSCRIBE_APP
|
|
13543
|
+
toolkit: whatsapp
|
|
13544
|
+
description: Subscribe your application to receive webhook notifications for a WhatsApp Business Account (WABA). This action registers the application to receive webhook events from the specified WABA. Use this ac...
|
|
13545
|
+
scopes:
|
|
13546
|
+
- whatsapp_business_management
|
|
13547
|
+
- whatsapp_business_messaging
|
|
13548
|
+
breadth: generic
|
|
13549
|
+
guarantee: experimental
|
|
13550
|
+
side_effect: write
|
|
13551
|
+
- name: whatsapp.update_business_account
|
|
13552
|
+
slug: WHATSAPP_UPDATE_BUSINESS_ACCOUNT
|
|
13553
|
+
toolkit: whatsapp
|
|
13554
|
+
description: Update metadata for a WhatsApp Flow including its name, categories, and endpoint URI. Use when you need to modify the configuration or settings of an existing WhatsApp Flow.
|
|
13555
|
+
scopes:
|
|
13556
|
+
- whatsapp_business_management
|
|
13557
|
+
breadth: generic
|
|
13558
|
+
guarantee: experimental
|
|
13559
|
+
side_effect: write
|
|
13560
|
+
- name: whatsapp.update_business_profile
|
|
13561
|
+
slug: WHATSAPP_UPDATE_BUSINESS_PROFILE
|
|
13562
|
+
toolkit: whatsapp
|
|
13563
|
+
description: Update WhatsApp Business Profile information including business details, contact information, and profile settings. Use when you need to modify the business profile settings such as the about text, bu...
|
|
13564
|
+
scopes:
|
|
13565
|
+
- whatsapp_business_management
|
|
13566
|
+
- whatsapp_business_messaging
|
|
13567
|
+
breadth: generic
|
|
13568
|
+
guarantee: experimental
|
|
13569
|
+
side_effect: write
|
|
13570
|
+
- name: whatsapp.update_commerce_settings
|
|
13571
|
+
slug: WHATSAPP_UPDATE_COMMERCE_SETTINGS
|
|
13572
|
+
toolkit: whatsapp
|
|
13573
|
+
description: Set or update commerce settings for a WhatsApp Business phone number. Use this action to enable or disable shopping cart functionality and product catalog visibility for a specific WhatsApp Business p...
|
|
13574
|
+
scopes:
|
|
13575
|
+
- whatsapp_business_management
|
|
13576
|
+
- whatsapp_business_messaging
|
|
13577
|
+
breadth: generic
|
|
13578
|
+
guarantee: experimental
|
|
13579
|
+
side_effect: write
|
|
13580
|
+
- name: whatsapp.update_flow_json
|
|
13581
|
+
slug: WHATSAPP_UPDATE_FLOW_JSON
|
|
13582
|
+
toolkit: whatsapp
|
|
13583
|
+
description: Update the Flow JSON configuration for a WhatsApp Flow. Use this action when you need to update or upload a new flow.json file for an existing WhatsApp Flow. The Flow JSON defines the structure, scree...
|
|
13584
|
+
scopes:
|
|
13585
|
+
- whatsapp_business_management
|
|
13586
|
+
- whatsapp_business_messaging
|
|
13587
|
+
breadth: generic
|
|
13588
|
+
guarantee: experimental
|
|
13589
|
+
side_effect: write
|
|
13590
|
+
- name: whatsapp.update_settings
|
|
13591
|
+
slug: WHATSAPP_UPDATE_SETTINGS
|
|
13592
|
+
toolkit: whatsapp
|
|
13593
|
+
description: Update Cloud API settings for a WhatsApp Business phone number. Use this action to modify calling features, user identity verification settings, payload encryption configuration, and data storage pref...
|
|
13594
|
+
scopes:
|
|
13595
|
+
- whatsapp_business_management
|
|
13596
|
+
- whatsapp_business_messaging
|
|
13597
|
+
breadth: generic
|
|
13598
|
+
guarantee: experimental
|
|
13599
|
+
side_effect: write
|
|
13600
|
+
- name: whatsapp.upload_media
|
|
13601
|
+
slug: WHATSAPP_UPLOAD_MEDIA
|
|
13602
|
+
toolkit: whatsapp
|
|
13603
|
+
description: 'Upload media files (images, videos, audio, documents, stickers) to WhatsApp servers. The uploaded media gets a media ID that can be used in send_media or other messaging actions. Supported formats: -...'
|
|
13604
|
+
scopes:
|
|
13605
|
+
- whatsapp_business_messaging
|
|
13606
|
+
breadth: generic
|
|
13607
|
+
guarantee: experimental
|
|
13608
|
+
side_effect: write
|
|
13609
|
+
- name: whatsapp.upsert_message_template
|
|
13610
|
+
slug: WHATSAPP_UPSERT_MESSAGE_TEMPLATE
|
|
13611
|
+
toolkit: whatsapp
|
|
13612
|
+
description: Create or update a message template for the WhatsApp Business Account. Use this action when you need to create a new message template or update an existing one with the same name. If a template with t...
|
|
13613
|
+
scopes:
|
|
13614
|
+
- whatsapp_business_management
|
|
13615
|
+
- whatsapp_business_messaging
|
|
13616
|
+
breadth: generic
|
|
13617
|
+
guarantee: experimental
|
|
13618
|
+
side_effect: write
|
|
13619
|
+
- name: whatsapp.verify_code
|
|
13620
|
+
slug: WHATSAPP_VERIFY_CODE
|
|
13621
|
+
toolkit: whatsapp
|
|
13622
|
+
description: Verify the OTP code for a pre-verified phone number. Use when you need to complete phone number verification by submitting the received OTP code. The phone number must first be added to your WhatsApp...
|
|
13623
|
+
scopes:
|
|
13624
|
+
- whatsapp_business_management
|
|
13625
|
+
- whatsapp_business_messaging
|
|
13626
|
+
breadth: generic
|
|
13627
|
+
guarantee: experimental
|
|
13628
|
+
side_effect: write
|
|
13629
|
+
- name: whatsapp.who_am_i
|
|
13630
|
+
slug: WHATSAPP_WHO_AM_I
|
|
13631
|
+
toolkit: whatsapp
|
|
13632
|
+
description: Return the identity (name) of the connected WhatsApp (Meta) account.
|
|
13633
|
+
scopes: []
|
|
13634
|
+
breadth: generic
|
|
13635
|
+
guarantee: experimental
|
|
13636
|
+
side_effect: read
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
"""Standard 5-field cron,
|
|
1
|
+
"""Standard 5-field cron, in whatever timezone the `after` datetime carries (gravity
|
|
2
|
+
schedule passes Asia/Kolkata, like the hosted schedules). Day-of-week 0 or 7 is Sunday; when both day
|
|
2
3
|
fields are restricted, either matching is enough (as in cron itself)."""
|
|
3
4
|
|
|
4
5
|
from __future__ import annotations
|
|
5
6
|
|
|
7
|
+
import math
|
|
6
8
|
from datetime import datetime, timedelta
|
|
9
|
+
from itertools import pairwise
|
|
7
10
|
|
|
8
11
|
_BOUNDS = ((0, 59), (0, 23), (1, 31), (1, 12), (0, 7))
|
|
12
|
+
# Every tick is a paid run; the control plane's MIN_SCHEDULE_MINUTES.
|
|
13
|
+
MIN_GAP_MINUTES = 5
|
|
9
14
|
|
|
10
15
|
|
|
11
16
|
def _values(field: str, low: int, high: int) -> set[int]:
|
|
@@ -22,6 +27,26 @@ def _values(field: str, low: int, high: int) -> set[int]:
|
|
|
22
27
|
return out
|
|
23
28
|
|
|
24
29
|
|
|
30
|
+
def field_values(expr: str) -> list[set[int]]:
|
|
31
|
+
"""Each field's values. ValueError when a field is malformed, empty or out of its range."""
|
|
32
|
+
out = [_values(f, lo, hi) for f, (lo, hi) in zip(expr.split(), _BOUNDS)]
|
|
33
|
+
for vals, (lo, hi) in zip(out, _BOUNDS):
|
|
34
|
+
if not vals or min(vals) < lo or max(vals) > hi:
|
|
35
|
+
raise ValueError(f"'{expr}' has a value out of range")
|
|
36
|
+
return out
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def min_gap(expr: str) -> float:
|
|
40
|
+
"""Minutes between the closest two fires, from the minute and hour fields alone (the day
|
|
41
|
+
fields can only widen it). Same arithmetic as the control plane's checkScheduleFloor."""
|
|
42
|
+
minutes, hours = field_values(expr)[:2]
|
|
43
|
+
mins = sorted(minutes)
|
|
44
|
+
gap = min((b - a for a, b in pairwise(mins)), default=math.inf)
|
|
45
|
+
if any((h + 1) % 24 in hours for h in hours):
|
|
46
|
+
gap = min(gap, 60 - mins[-1] + mins[0])
|
|
47
|
+
return gap
|
|
48
|
+
|
|
49
|
+
|
|
25
50
|
def matches(expr: str, at: datetime) -> bool:
|
|
26
51
|
fields = expr.split()
|
|
27
52
|
minute, hour, dom, month, dow = (_values(f, *b) for f, b in zip(fields, _BOUNDS))
|
|
@@ -9,6 +9,7 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_valida
|
|
|
9
9
|
from pydantic_core import PydanticCustomError
|
|
10
10
|
|
|
11
11
|
from . import contract as C
|
|
12
|
+
from . import cron
|
|
12
13
|
from .errors import ErrorCode
|
|
13
14
|
|
|
14
15
|
# Unknown fields are rejected, so a newer-contract field is reported, not dropped.
|
|
@@ -99,6 +100,18 @@ class RuntimeSpec(BaseModel):
|
|
|
99
100
|
)
|
|
100
101
|
return v
|
|
101
102
|
|
|
103
|
+
@field_validator("python", mode="before")
|
|
104
|
+
@classmethod
|
|
105
|
+
def _check_python_is_text(cls, v: Any) -> Any:
|
|
106
|
+
# YAML reads an unquoted 3.12 as a float (and 3.10 as 3.1).
|
|
107
|
+
if isinstance(v, (int, float)):
|
|
108
|
+
raise PydanticCustomError(
|
|
109
|
+
ErrorCode.INVALID_PYTHON_VERSION.value,
|
|
110
|
+
"Python version {value} is not quoted text.",
|
|
111
|
+
{"value": v, "remediation": 'Put it in quotes: python: "3.12".'},
|
|
112
|
+
)
|
|
113
|
+
return v
|
|
114
|
+
|
|
102
115
|
@field_validator("python")
|
|
103
116
|
@classmethod
|
|
104
117
|
def _check_python(cls, v: str) -> str:
|
|
@@ -375,6 +388,37 @@ class ScheduleSpec(BaseModel):
|
|
|
375
388
|
),
|
|
376
389
|
},
|
|
377
390
|
)
|
|
391
|
+
try:
|
|
392
|
+
cron.field_values(v)
|
|
393
|
+
except ValueError:
|
|
394
|
+
raise PydanticCustomError(
|
|
395
|
+
ErrorCode.INVALID_TRIGGER.value,
|
|
396
|
+
"Schedule '{value}' has a value out of range.",
|
|
397
|
+
{
|
|
398
|
+
"value": v,
|
|
399
|
+
"remediation": (
|
|
400
|
+
"Minutes are 0-59, hours 0-23, days 1-31, months 1-12 and "
|
|
401
|
+
"weekdays 0-7 (0 and 7 are Sunday)."
|
|
402
|
+
),
|
|
403
|
+
},
|
|
404
|
+
) from None
|
|
405
|
+
# The platform's scheduler cannot express cron's "either day matches".
|
|
406
|
+
if fields[2] != "*" and fields[4] != "*":
|
|
407
|
+
raise PydanticCustomError(
|
|
408
|
+
ErrorCode.INVALID_TRIGGER.value,
|
|
409
|
+
"Schedule '{value}' restricts both the day of the month and the weekday.",
|
|
410
|
+
{"value": v, "remediation": "Set one of them to *."},
|
|
411
|
+
)
|
|
412
|
+
if cron.min_gap(v) < cron.MIN_GAP_MINUTES:
|
|
413
|
+
raise PydanticCustomError(
|
|
414
|
+
ErrorCode.INVALID_TRIGGER.value,
|
|
415
|
+
"Schedules can run at most every {limit} minutes.",
|
|
416
|
+
{
|
|
417
|
+
"value": v,
|
|
418
|
+
"limit": cron.MIN_GAP_MINUTES,
|
|
419
|
+
"remediation": f"Use '*/{cron.MIN_GAP_MINUTES} * * * *' or something slower.",
|
|
420
|
+
},
|
|
421
|
+
)
|
|
378
422
|
return v
|
|
379
423
|
|
|
380
424
|
|
|
@@ -527,6 +571,18 @@ class Manifest(BaseModel):
|
|
|
527
571
|
)
|
|
528
572
|
return v
|
|
529
573
|
|
|
574
|
+
@field_validator("version", mode="before")
|
|
575
|
+
@classmethod
|
|
576
|
+
def _check_version_is_text(cls, v: Any) -> Any:
|
|
577
|
+
# YAML reads an unquoted 1.0 as a float.
|
|
578
|
+
if isinstance(v, (int, float)):
|
|
579
|
+
raise PydanticCustomError(
|
|
580
|
+
ErrorCode.INVALID_AGENT_VERSION.value,
|
|
581
|
+
"Agent version {value} is not quoted text.",
|
|
582
|
+
{"value": v, "remediation": 'Put it in quotes: version: "1.0.0".'},
|
|
583
|
+
)
|
|
584
|
+
return v
|
|
585
|
+
|
|
530
586
|
@field_validator("version")
|
|
531
587
|
@classmethod
|
|
532
588
|
def _check_version(cls, v: str) -> str:
|
|
@@ -156,7 +156,17 @@ def load_manifest_file(
|
|
|
156
156
|
"Run `gravity init` to create one, or check the path.",
|
|
157
157
|
str(p),
|
|
158
158
|
)
|
|
159
|
-
|
|
159
|
+
try:
|
|
160
|
+
text = p.read_text(encoding="utf-8")
|
|
161
|
+
except UnicodeDecodeError:
|
|
162
|
+
# Notepad's "Unicode" is UTF-16; a BOM-only UTF-8 file decodes fine and YAML skips the BOM.
|
|
163
|
+
raise _fail(
|
|
164
|
+
ErrorCode.MALFORMED_YAML,
|
|
165
|
+
"Manifest is not UTF-8 text.",
|
|
166
|
+
"Save the file as UTF-8 (in Notepad: Encoding, then UTF-8).",
|
|
167
|
+
str(p),
|
|
168
|
+
) from None
|
|
169
|
+
return load_manifest_text(text, catalog=catalog)
|
|
160
170
|
|
|
161
171
|
|
|
162
172
|
def _fail(code: ErrorCode, message: str, remediation: str, location: str = "") -> ManifestValidationError:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
|
-
from gravity_schema.cron import matches, next_run
|
|
4
|
+
from gravity_schema.cron import field_values, matches, min_gap, next_run
|
|
5
5
|
|
|
6
6
|
MON_0900 = datetime(2026, 9, 28, 9, 0) # a Monday
|
|
7
7
|
|
|
@@ -29,3 +29,18 @@ def test_next_run_is_strictly_after():
|
|
|
29
29
|
assert next_run("0 9 * * *", MON_0900) == datetime(2026, 9, 29, 9, 0)
|
|
30
30
|
with pytest.raises(ValueError):
|
|
31
31
|
next_run("0 0 31 2 *", MON_0900) # 31 February
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_field_values_checks_every_range():
|
|
35
|
+
assert field_values("0 9 1 * 7")[4] == {7}
|
|
36
|
+
for bad in ("60 * * * *", "0 24 * * *", "0 0 0 * *", "0 0 32 * *", "0 0 * 13 *", "0 0 * * 8", "5-3 * * * *", "*/0 * * * *"):
|
|
37
|
+
with pytest.raises(ValueError):
|
|
38
|
+
field_values(bad)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_min_gap_is_the_closest_two_fires():
|
|
42
|
+
assert min_gap("*/5 * * * *") == 5
|
|
43
|
+
assert min_gap("0,3 * * * *") == 3
|
|
44
|
+
assert min_gap("0,59 * * * *") == 1 # 10:59 then 11:00
|
|
45
|
+
assert min_gap("59 9 * * *") == float("inf") # once a day; the day fields don't matter
|
|
46
|
+
assert min_gap("58 9,10 * * *") == 60
|
|
@@ -8,9 +8,12 @@ from gravity_schema import (
|
|
|
8
8
|
InputType,
|
|
9
9
|
ManifestValidationError,
|
|
10
10
|
ModelTier,
|
|
11
|
+
load_manifest_file,
|
|
11
12
|
validate_manifest,
|
|
12
13
|
)
|
|
13
14
|
from gravity_schema.contract import MAX_INPUTS, MAX_TOOLS
|
|
15
|
+
from gravity_schema.manifest import ScheduleSpec
|
|
16
|
+
from pydantic import ValidationError
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
def codes(exc: ManifestValidationError) -> set[ErrorCode]:
|
|
@@ -122,6 +125,32 @@ def test_valid_semver_accepted(manifest_data, good):
|
|
|
122
125
|
assert validate_manifest(manifest_data).version == good
|
|
123
126
|
|
|
124
127
|
|
|
128
|
+
@pytest.mark.parametrize("bad", [1.0, 1])
|
|
129
|
+
def test_unquoted_version_says_to_quote_it(manifest_data, bad):
|
|
130
|
+
"""`version: 1.0` is a YAML float; pydantic's bare 'Input should be a valid string' has no fix."""
|
|
131
|
+
manifest_data["version"] = bad
|
|
132
|
+
|
|
133
|
+
with pytest.raises(ManifestValidationError) as ei:
|
|
134
|
+
validate_manifest(manifest_data)
|
|
135
|
+
|
|
136
|
+
issue = only_issue(ei.value)
|
|
137
|
+
assert issue.code is ErrorCode.INVALID_AGENT_VERSION
|
|
138
|
+
assert issue.location == "version"
|
|
139
|
+
assert 'version: "1.0.0"' in (issue.remediation or "")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_unquoted_python_says_to_quote_it(manifest_data):
|
|
143
|
+
manifest_data["runtime"]["python"] = 3.12
|
|
144
|
+
|
|
145
|
+
with pytest.raises(ManifestValidationError) as ei:
|
|
146
|
+
validate_manifest(manifest_data)
|
|
147
|
+
|
|
148
|
+
issue = only_issue(ei.value)
|
|
149
|
+
assert issue.code is ErrorCode.INVALID_PYTHON_VERSION
|
|
150
|
+
assert issue.location == "runtime.python"
|
|
151
|
+
assert 'python: "3.12"' in (issue.remediation or "")
|
|
152
|
+
|
|
153
|
+
|
|
125
154
|
# Runtime
|
|
126
155
|
|
|
127
156
|
|
|
@@ -575,6 +604,59 @@ def test_invalid_cron_rejected(manifest_data, bad):
|
|
|
575
604
|
assert ErrorCode.INVALID_TRIGGER in rejected_with(manifest_data)
|
|
576
605
|
|
|
577
606
|
|
|
607
|
+
@pytest.mark.parametrize("bad", ["60 * * * *", "0 25 * * *", "0 8 32 * *", "0 8 1 13 *", "0 8 * * 8", "*/0 * * * *", "5-3 * * * *"])
|
|
608
|
+
def test_cron_value_out_of_range_rejected(manifest_data, bad):
|
|
609
|
+
"""These passed as '5 fields of digits' but never fire, or the platform refuses them."""
|
|
610
|
+
manifest_data["triggers"] = {"schedule": {"cron": bad}}
|
|
611
|
+
|
|
612
|
+
with pytest.raises(ManifestValidationError) as ei:
|
|
613
|
+
validate_manifest(manifest_data)
|
|
614
|
+
|
|
615
|
+
issue = only_issue(ei.value)
|
|
616
|
+
assert issue.code is ErrorCode.INVALID_TRIGGER
|
|
617
|
+
assert "0-59" in (issue.remediation or "")
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def test_cron_restricting_both_day_fields_rejected(manifest_data):
|
|
621
|
+
"""Unix cron ORs them; the platform's scheduler cannot."""
|
|
622
|
+
manifest_data["triggers"] = {"schedule": {"cron": "0 9 1 * 1"}}
|
|
623
|
+
|
|
624
|
+
with pytest.raises(ManifestValidationError) as ei:
|
|
625
|
+
validate_manifest(manifest_data)
|
|
626
|
+
|
|
627
|
+
issue = only_issue(ei.value)
|
|
628
|
+
assert issue.code is ErrorCode.INVALID_TRIGGER
|
|
629
|
+
assert "Set one of them to *" in (issue.remediation or "")
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
@pytest.mark.parametrize("bad", ["* * * * *", "*/2 * * * *", "*/4 * * * *", "0,3 * * * *", "0,59 * * * *", "0,2 9 * * *"])
|
|
633
|
+
def test_cron_faster_than_every_5_minutes_rejected(manifest_data, bad):
|
|
634
|
+
"""The control plane's floor (checkScheduleFloor), with its wording, caught before push."""
|
|
635
|
+
manifest_data["triggers"] = {"schedule": {"cron": bad}}
|
|
636
|
+
|
|
637
|
+
with pytest.raises(ManifestValidationError) as ei:
|
|
638
|
+
validate_manifest(manifest_data)
|
|
639
|
+
|
|
640
|
+
issue = only_issue(ei.value)
|
|
641
|
+
assert issue.code is ErrorCode.INVALID_TRIGGER
|
|
642
|
+
assert issue.message == "Schedules can run at most every 5 minutes."
|
|
643
|
+
assert issue.remediation
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
@pytest.mark.parametrize("good", ["*/5 * * * *", "0,5 9 * * *", "0,30 * * * *", "59 23 * * *", "58 9,10 * * *", "0 9 1 * *", "0 9 * * 7"])
|
|
647
|
+
def test_cron_at_or_above_the_floor_accepted(manifest_data, good):
|
|
648
|
+
manifest_data["triggers"] = {"schedule": {"cron": good}}
|
|
649
|
+
assert validate_manifest(manifest_data).triggers.schedule.cron == good
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def test_schedule_spec_alone_applies_the_same_rules():
|
|
653
|
+
"""`gravity schedule --cron` validates through ScheduleSpec directly."""
|
|
654
|
+
with pytest.raises(ValidationError) as ei:
|
|
655
|
+
ScheduleSpec(cron="*/2 * * * *")
|
|
656
|
+
assert "Schedules can run at most every 5 minutes." in str(ei.value)
|
|
657
|
+
assert ScheduleSpec(cron="*/5 * * * *").cron == "*/5 * * * *"
|
|
658
|
+
|
|
659
|
+
|
|
578
660
|
def test_an_agent_nothing_can_start_is_rejected(manifest_data):
|
|
579
661
|
manifest_data["triggers"] = {"manual": False}
|
|
580
662
|
assert ErrorCode.INVALID_TRIGGER in rejected_with(manifest_data)
|
|
@@ -626,6 +708,30 @@ def test_runtime_python_defaults_to_313(manifest_data):
|
|
|
626
708
|
assert validate_manifest(manifest_data).runtime.python == "3.12"
|
|
627
709
|
|
|
628
710
|
|
|
711
|
+
# Loading from disk (the path every gravity command takes)
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
def test_utf16_manifest_file_reports_the_encoding(tmp_path):
|
|
715
|
+
"""Notepad's 'Unicode' is UTF-16; it must be a problem, not a UnicodeDecodeError traceback."""
|
|
716
|
+
p = tmp_path / "manifest.yaml"
|
|
717
|
+
p.write_bytes("contract: v1\nname: x\n".encode("utf-16"))
|
|
718
|
+
|
|
719
|
+
with pytest.raises(ManifestValidationError) as ei:
|
|
720
|
+
load_manifest_file(p)
|
|
721
|
+
|
|
722
|
+
issue = only_issue(ei.value)
|
|
723
|
+
assert issue.code is ErrorCode.MALFORMED_YAML
|
|
724
|
+
assert issue.location == str(p)
|
|
725
|
+
assert "UTF-8" in (issue.remediation or "")
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def test_utf8_bom_manifest_file_still_loads(tmp_path):
|
|
729
|
+
"""Notepad's default 'UTF-8 with BOM' is fine: YAML skips the BOM."""
|
|
730
|
+
p = tmp_path / "manifest.yaml"
|
|
731
|
+
p.write_bytes(b"\xef\xbb\xbf" + b"name: x\nruntime:\n entrypoint: src/agent.py:graph\n")
|
|
732
|
+
assert load_manifest_file(p).name == "x"
|
|
733
|
+
|
|
734
|
+
|
|
629
735
|
def test_a_secret_input_cannot_have_a_default(manifest_data):
|
|
630
736
|
manifest_data["inputs"] = [{"name": "api_key", "type": "secret", "label": "Key", "default": "sk-x"}]
|
|
631
737
|
with pytest.raises(ManifestValidationError) as e:
|
|
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
|