← Files Zuora Coding AgentARCHIVED FILE
references/workflow-enums.json
30.7 KB · Oct 2, 2026 · 00:30 UTC
{
"$description": "Global enums and special rules shared across all Zuora Workflow tasks. Distilled from Task.action_type (app/models/task.rb L636-681), Workflow::Setup validations, Linkage model, and BusinessEvent.",
"action_types": [
"GraphQuery",
"Data::Aqua",
"Data::BillingPreviewRun",
"Data::Link",
"Export",
"Query",
"If",
"Logic::Case",
"Iterate",
"Logic::Liquid",
"Logic::Lambda",
"Logic::CSVTranslator",
"Logic::XMLTransform",
"Logic::JSONTransform",
"Logic::ResponseFormatter",
"Logic::Merge",
"Script::JavaScript",
"UI::Page",
"UI::Stop",
"UI::WebShare",
"Create",
"Update",
"Delete",
"CustomObject::Create",
"CustomObject::Update",
"CustomObject::Delete",
"CustomObject::Query",
"Attachment",
"File::CustomPDF::CustomDocument",
"File::DownloadFile",
"File::ZuoraImport",
"File::FileOperations",
"File::FileStreamingUpload",
"File::BulkDataLoader",
"Reporting::RunReport",
"Reporting::OracleFusionReport",
"NewProduct",
"RemoveProduct",
"Suspend",
"Resume",
"Cancel",
"Billing::BillRun",
"InvoiceGenerate",
"WriteOff",
"Billing::ReverseInvoice",
"Billing::CurrencyConversion",
"Billing::CustomBillingDocument",
"Payment::PaymentRun",
"Payment::GatewayReconciliation",
"Usage::ImportUsage",
"Email",
"Callout",
"Notifications::SMS",
"AsynchronousCallout",
"Notifications::Kafka",
"Execute::WorkflowTask",
"Approval",
"Upload::FTP",
"Upload::SFTP",
"Upload::S3",
"Download::SFTP",
"Download::S3",
"Delay",
"UsageMediation::Source",
"UsageMediation::Watermark",
"UsageMediation::Filter",
"UsageMediation::Map",
"UsageMediation::Group",
"UsageMediation::Sink",
"Mediation::SendEvents"
],
"workflow_call_types": {
"$description": "Sourced from Task.task_mode keys (app/models/task.rb L152-165) filtered by WorkflowsController#index L20-21. Each entry lists tenant prerequisites and recommended task types. The composer must NEVER emit ASYNC, RULE, or UI - these are deprecated/internal and rejected by the linter rule E150.",
"user_facing": [
{
"value": "BATCH",
"default": true,
"description": "Standard background workflow. Most reporting, integration, and scheduled jobs.",
"requires_extra_setting": null
},
{
"value": "SYNC",
"description": "Synchronous response. Caller waits for the workflow to finish and receives the result inline. Each task class must declare task_mode['SYNC'] => true.",
"requires_extra_setting": "synchronous_mode"
},
{
"value": "REALTIME",
"description": "Sub-second priority lane for low-latency callouts.",
"requires_extra_setting": null
},
{
"value": "UIACTION",
"description": "Workflow surfaces as a button on a Zuora UI page. Requires ui_pages with exactly one supported_ui_pages entry.",
"requires_extra_setting": null
},
{
"value": "SYNC_UI_ACTION",
"description": "Synchronous UIACTION variant. Caller blocks on the UI thread.",
"requires_extra_setting": "sync_ui_action_mode"
},
{
"value": "DATASTREAM",
"description": "Streaming pipeline (UsageMediation::* tasks). Persists state across runs.",
"requires_extra_setting": "data_stream_mode"
},
{
"value": "BUSINESS_PROCESS",
"description": "Long-running business process (Approval, Delay, Page tasks).",
"requires_extra_setting": "business_process"
}
],
"deprecated_or_internal": ["ASYNC", "RULE", "UI"]
},
"workflow_priorities": ["High", "Medium", "Low"],
"workflow_statuses": ["Active", "Inactive"],
"trigger_flags": [
"ondemand_trigger",
"callout_trigger",
"scheduled_trigger",
"event_trigger"
],
"version_regex": "^\\d+(?:\\.\\d+)?(?:\\.\\d+)?$",
"standard_events": {
"$description": "Hard-coded list mirroring defaultEvents in app/javascript/components/WorkflowDefinitionForm.js L150-219. These are the standard Zuora business events that the React settings page exposes WITHOUT requiring a custom event registration. For anything not in this list, the tenant must first POST /events/event-triggers (or use the Settings -> Notifications -> Custom Events UI). Use field 'name' verbatim in workflow.parameters.event_triggers[].",
"$category_derivation": "For id length < 5 chars, category = id (e.g., '1410' for BillingRunCompletion). Otherwise, category = ${namespace}:${name}. Used as the query parameter for GET /notifications/email-templates/info/selections?category=<category>.",
"$canonical_name_corrections": {
"BillRunCompleted": "BillingRunCompletion",
"BillRunCompletedSuccess": "BillingRunCompletion",
"BillingRunCompleted": "BillingRunCompletion",
"InvoiceCreated": "InvoicePosted",
"PaymentReceived": "PaymentProcessed"
},
"events": [
{ "name": "AmendmentProcessed", "id": "1310", "baseObject": "Amendment" },
{ "name": "AquaNotification", "id": "2610", "baseObject": "Aqua" },
{ "name": "BillingRunCompletion", "id": "1410", "baseObject": "BillingRun" },
{ "name": "CreditBalanceRefundProcessed", "id": "2420", "baseObject": "Refund" },
{ "name": "CreditMemoCreated", "id": "2710", "baseObject": "CreditMemo" },
{ "name": "CreditMemoPosted", "id": "2720", "baseObject": "CreditMemo" },
{ "name": "CreditMemoRefundProcessed", "id": "2430", "baseObject": "Refund" },
{ "name": "DataSourceOutputCompletion", "id": "1610", "baseObject": "DataSource" },
{ "name": "DebitMemoCreated", "id": "2810", "baseObject": "DebitMemo" },
{ "name": "DebitMemoPosted", "id": "2820", "baseObject": "DebitMemo" },
{ "name": "EmailCreditMemo", "id": "2730", "baseObject": "CreditMemo" },
{ "name": "EmailDebitMemo", "id": "2830", "baseObject": "DebitMemo" },
{ "name": "GatewayReconciliation", "id": "2150", "baseObject": "Payment" },
{ "name": "InvoiceDue", "id": "1140", "baseObject": "Invoice" },
{ "name": "InvoicePosted", "id": "1110", "baseObject": "Invoice" },
{ "name": "InvoicesPastDueAccountSummary", "id": "1120", "baseObject": "Account" },
{ "name": "JournalRunCompletion", "id": "3110", "baseObject": "JournalRun" },
{ "name": "KeyDates", "id": "1230", "baseObject": "Subscription" },
{ "name": "ManualEmailForInvoice", "id": "1130", "baseObject": "Invoice" },
{ "name": "ManualEmailForPayment", "id": "2130", "baseObject": "Payment" },
{ "name": "PaymentDeclined", "id": "2110", "baseObject": "Payment" },
{ "name": "PaymentMethodClosed", "id": "2230", "baseObject": "PaymentMethod" },
{ "name": "PaymentMethodExpiration", "id": "2210", "baseObject": "PaymentMethod" },
{ "name": "PaymentMethodUpdated", "id": "2220", "baseObject": "PaymentMethod" },
{ "name": "PaymentMethodUpdaterBatchCompleted", "id": "2520", "baseObject": "PaymentMethodUpdater" },
{ "name": "PaymentMethodUpdaterBatchStarted", "id": "2510", "baseObject": "PaymentMethodUpdater" },
{ "name": "PaymentProcessed", "id": "2120", "baseObject": "Payment" },
{ "name": "PaymentRefundProcessed", "id": "2410", "baseObject": "Refund" },
{ "name": "PaymentRunCompletion", "id": "2140", "baseObject": "Payment" },
{ "name": "SubscriptionCreated", "id": "1210", "baseObject": "Subscription" },
{ "name": "TrialBalanceCompletion", "id": "3210", "baseObject": "AccountingPeriod" },
{ "name": "UpcomingRenewal", "id": "1220", "baseObject": "Subscription" }
]
},
"supported_ui_pages": {
"$description": "Mirrors supportedUiPages in app/javascript/components/WorkflowDefinitionForm.js L223-249. Used to populate workflow.ui_pages when call_type == 'UIACTION'. Exactly one entry must be set with shape { '<value>': { 'label': '<button label>' } }.",
"pages": [
{ "value": "account", "label": "Account List Page", "role": "ZBilling" },
{ "value": "accountDetail", "label": "Account Detail Page", "role": "ZBilling" },
{ "value": "accountingPeriod", "label": "Accounting Period List Page", "role": "ZFinance" },
{ "value": "accountingPeriodDetail", "label": "Accounting Period Detail Page", "role": "ZFinance" },
{ "value": "billRun", "label": "Bill Run List Page", "role": "ZBilling" },
{ "value": "billRunDetail", "label": "Bill Run Detail Page", "role": "ZBilling" },
{ "value": "creditMemoDetail", "label": "Credit Memo Detail Page", "role": "ZBilling" },
{ "value": "creditDebitMemo", "label": "Credit & Debit Memo List Page", "role": "ZBilling" },
{ "value": "debitMemoDetail", "label": "Debit Memo Detail Page", "role": "ZBilling" },
{ "value": "invoice", "label": "Invoice List Page", "role": "ZBilling" },
{ "value": "invoiceDetail", "label": "Invoice Detail Page", "role": "ZBilling" },
{ "value": "journalRun", "label": "Journal Run List Page", "role": "ZFinance" },
{ "value": "journalRunDetail", "label": "Journal Run Detail Page", "role": "ZFinance" },
{ "value": "order", "label": "Order List Page", "role": "ZBilling" },
{ "value": "orderDetail", "label": "Order Detail Page", "role": "ZBilling" },
{ "value": "payment", "label": "Payment List Page", "role": "ZPayments" },
{ "value": "paymentDetail", "label": "Payment Detail Page", "role": "ZPayments" },
{ "value": "paymentRun", "label": "Payment Run List Page", "role": "ZPayments" },
{ "value": "paymentRunDetail", "label": "Payment Run Detail Page", "role": "ZPayments" },
{ "value": "product", "label": "Product Catalog List Page", "role": "ZBilling" },
{ "value": "productDetail", "label": "Product Catalog Detail Page", "role": "ZBilling" },
{ "value": "refund", "label": "Refund List Page", "role": "ZPayments" },
{ "value": "refundDetail", "label": "Refund Detail Page", "role": "ZPayments" },
{ "value": "subscription", "label": "Subscription List Page", "role": "ZBilling" },
{ "value": "subscriptionDetail", "label": "Subscription Detail Page", "role": "ZBilling" }
]
},
"parameters_subschema": {
"$description": "Default keys persisted by WorkflowsController#workflow_params L723-732 plus conditional keys L749-759. Composer should always set the seven default keys; the conditional keys are only emitted when the corresponding trigger style is in use.",
"always_present": {
"fields": "[] Array of inbound-payload field schemas. Used by callout_trigger workflows AND ondemand workflows that want a typed Run prompt.",
"entity_name": "null Optional. Display name of the Zuora entity (multi-entity tenants only). Auto-set by Workflow::Setup.import L435-442 when the tenant has exactly one entity attached.",
"entity_id": "null Optional. Entity UUID. Same auto-set rule as entity_name.",
"skipping_check": "\"db\" Constant. Always 'db'.",
"file_encryption": "\"false\" STRING boolean. Set to 'true' only if callout payload includes encrypted file uploads.",
"secure_error_msgs": "\"false\" STRING boolean. Set to 'true' to scrub sensitive data from error logs.",
"show_run_prompt": "null Optional. Hash of run-prompt UI config. Leave null unless the user wants a structured Run dialog.",
"callout_response": "\"workflow instance\" Default response payload returned by callout_trigger workflows. Other accepted values: 'first task', 'last task'."
},
"conditional_present": {
"event_triggers": "[] Array of event name strings (e.g., ['BillingRunCompletion']). Required when event_trigger == true.",
"event_parameters": "[] Array of {eventName, params: [{object, key, value}, ...]}. Required when event_trigger == true. See event_parameters_schema below.",
"merge_task_ids": "[] DO NOT EMIT. Auto-derived during Logic::Merge validation; Workflow::Setup.import L452, L468 deletes any inbound value before save. Linter rule W301.",
"legacy_boolean_input": "\"false\" Legacy. Only set for old workflows that depend on the to_bool semantic for Boolean form fields.",
"show_toast_envs": "[] Optional UI hint. Array of env names from ['Sandbox', 'Test', 'Production']."
}
},
"event_parameters_schema": {
"$description": "Shape consumed by BusinessEvent#parse_event at runtime. params_settings = (workflow.parameters['event_parameters'] || []).select{|p| p['eventName']==data['name']}.dig(0, 'params') || []; params_settings.each {|p| p['object'], p['key'], p['value']}. Both event_parameters AND the inner params MUST be ARRAYS (not Hashes), even though the React UI emits Object.assign({}, currParams) which serializes as an object - that is a UI quirk that breaks multi-param events at runtime. Always emit arrays from the composer.",
"shape": [
{
"eventName": "<must match an entry in workflow.parameters.event_triggers[]>",
"params": [
{
"object": "<base object, e.g., 'BillingRun', 'Invoice', 'Payment'>",
"key": "<field path on the event, e.g., 'Id', 'AccountId', 'Status'>",
"value": "<Liquid placeholder using <Event.Object.Field> syntax, e.g., '<BillingRun.Id>'>"
}
]
}
],
"field_options_api": {
"method": "GET",
"path": "/notifications/email-templates/info/selections?category=<category>",
"category_derivation": "If event.id.length < 5 then category = event.id; else category = '${namespace}:${eventName}'. See WorkflowSettingsForm.js L383-387 and app_instance.rb#get_custom_event_fields L1452-1494.",
"filter_rules": "For Standard events (those in standard_events.events), exclude any returned field containing 'DataSource'. For Custom events, include only fields with 'DataSource' or matching '.${baseObject}.'."
}
},
"notifications_schema": {
"$description": "Default shape persisted via Workflow#cast (workflow.rb L188-216). All boolean keys are coerced via to_bool. emails[] entries are Liquid-templated strings (e.g., '{{Data.Account.WorkEmail__c}}').",
"shape": {
"emails": "[] Array of email strings. May contain Liquid templates.",
"failure": "false Send notification when workflow ends in error.",
"success": "false Send notification when workflow ends successfully.",
"pending": "false Send notification when a Pending task remains queued past threshold.",
"skipped_scheduled_run": "false Send notification when a scheduled run is skipped because the prior run was still in flight.",
"error_ignore": "\"\" Optional Ruby regex. Errors matching this pattern are NOT included in failure notifications. Validated by Regexp.new (workflow/setup.rb L149)."
},
"validation": {
"rule": "If any of failure/success/pending/skipped_scheduled_run is true, emails[] must contain at least one non-blank entry (workflow/setup.rb L194-196)."
}
},
"interval_schema": {
"$description": "6-token Rufus/Fugit cron parsed by Rufus::Scheduler.parse (workflow/setup.rb L161-166). Tokens: <sec> <min> <hour> <day-of-month> <month> <day-of-week>. The React CronScheduler emits 6 tokens with second=0 (workflow/rails/app/javascript/components/CronScheduler.js L33-58). The /N syntax (no leading asterisk) is shorthand for */N (e.g., '/5' = every 5). Day-of-week names like 'MON-FRI' and ordinals like 'MON#2' (= second Monday of the month) are accepted.",
"examples": {
"every_5_minutes": "0 /5 * /1 * *",
"every_hour_at_30": "0 30 /1 /1 * *",
"daily_at_09_30": "0 30 09 /1 * *",
"weekdays_at_08_00": "0 0 08 * * MON-FRI",
"first_of_month_midnight": "0 0 0 1 * *",
"second_monday_of_month_06_00": "0 0 06 * * MON#2",
"monday_at_06_00": "0 0 06 * * MON"
},
"$runtime_assembly": "At schedule time Rails concatenates the interval and the IANA-resolved timezone: \"#{self.interval} #{ActiveSupport::TimeZone.find_tzinfo(self.timezone).name}\" (workflow/rails/app/models/workflow.rb L336). Rufus consumes the trailing token as the timezone.",
"timezone": {
"$description": "Rails ActiveSupport friendly timezone name (e.g. 'Eastern Time (US & Canada)', 'Alaska', 'UTC', 'London', 'Tokyo'). Validated by `validates :timezone, inclusion: ActiveSupport::TimeZone.all.map(&:name)` (workflow/rails/app/models/workflow/setup.rb L32). NOT a bare IANA name -- 'America/New_York' fails validation; use 'Eastern Time (US & Canada)' instead.",
"allowlist_source": "rails-timezones.json (mapping keys = friendly names; values = IANA targets resolved at runtime via ActiveSupport::TimeZone.find_tzinfo(name).name).",
"examples": ["Eastern Time (US & Canada)", "Central Time (US & Canada)", "Mountain Time (US & Canada)", "Pacific Time (US & Canada)", "Alaska", "Hawaii", "London", "Berlin", "Paris", "Tokyo", "Sydney", "UTC"],
"common_iana_to_friendly": {
"America/New_York": "Eastern Time (US & Canada)",
"America/Chicago": "Central Time (US & Canada)",
"America/Denver": "Mountain Time (US & Canada)",
"America/Los_Angeles": "Pacific Time (US & Canada)",
"Europe/London": "London",
"Europe/Berlin": "Berlin",
"Asia/Tokyo": "Tokyo",
"Australia/Sydney": "Sydney"
}
}
},
"default_hooks": ["Success", "Failure"],
"predictability_categories": {
"$description": "Five-way classification used by data-flow analysis (workflow-data-flow.md, lint-workflow-json.js rules E170/W171/W172/W173/W174). Each task entry in workflow-task-templates.json carries data_contract.predictability set to one of these values. The fallback ($default_data_contract) is 'opaque' so the linter is conservative for unknown task types.",
"deterministic": {
"$summary": "Both the scope (Data.X) and the field shape (X.field1, X.field2, ...) are known from the task model + parameters at design time.",
"examples": ["Query", "Create", "Update", "CustomObject::Query", "CustomObject::Create", "CustomObject::Update", "NewProduct", "RemoveProduct", "Suspend", "Resume", "Cancel", "InvoiceGenerate", "WriteOff", "Billing::ReverseInvoice", "Payment::GatewayReconciliation"],
"linter_validates": "scope + field-level"
},
"semi-deterministic": {
"$summary": "The scope is known; field shape is partially known (standardized model fields), or contents come from a free-form expression (GraphQL selection set, Liquid assigns, report column names).",
"examples": ["Export", "Billing::BillRun", "Payment::PaymentRun", "Data::BillingPreviewRun", "Data::Aqua", "Data::Link", "GraphQuery", "Logic::Liquid", "Reporting::RunReport", "Reporting::OracleFusionReport", "File::DownloadFile", "File::ZuoraImport", "File::FileOperations", "File::FileStreamingUpload", "File::BulkDataLoader", "File::CustomPDF::CustomDocument", "Billing::CurrencyConversion", "Billing::CustomBillingDocument", "Usage::ImportUsage", "Attachment", "Download::SFTP", "Download::S3"],
"linter_validates": "scope-level only; W171 downgraded to a notice when fields_partial_known=true"
},
"opaque": {
"$summary": "Scope is known (often via parameters.placement), but field shape is unknowable until runtime. Requires user to declare a schema, opt out, or insert a normalizer (see Build Step 3e).",
"examples": ["Callout", "AsynchronousCallout", "Logic::Lambda", "Script::JavaScript", "Logic::JSONTransform", "Logic::XMLTransform", "Logic::CSVTranslator", "Logic::ResponseFormatter", "Execute::WorkflowTask", "Mediation::SendEvents"],
"linter_validates": "scope-level only; downstream Data.<scope>.<field> references emit W172 unless suppressed via parameters._opaque_trusted='true' or parameters._expected_response_schema={...}"
},
"scoping": {
"$summary": "No positive writes; routes execution along branches (If/Logic::Case/Approval) and/or rebinds an existing scope (Iterate/Logic::Merge/Delete).",
"examples": ["If", "Logic::Case", "Iterate", "Logic::Merge", "Approval", "Delete", "CustomObject::Delete"],
"linter_validates": "Iterate triggers W173 for array-shape references inside For-Each body; Logic::Case triggers W174 for branch-partial scopes after Logic::Merge."
},
"none": {
"$summary": "Side-effect-only or non-data tasks: writes nothing into Data.* that downstream tasks can consume.",
"examples": ["Email", "Notifications::SMS", "Notifications::Kafka", "Delay", "UI::Page", "UI::Stop", "UI::WebShare", "Upload::FTP", "Upload::SFTP", "Upload::S3", "UsageMediation::Source", "UsageMediation::Watermark", "UsageMediation::Filter", "UsageMediation::Map", "UsageMediation::Group", "UsageMediation::Sink"],
"linter_validates": "no contribution to available_scopes"
}
},
"opaque_sentinel_parameters": {
"$description": "Sentinel keys the build skill / linter look for INSIDE an opaque task's parameters block. Rails ignores unknown parameters keys (it's free-form JSONB), so these have zero runtime impact. The build skill (Step 3e) must elicit one of these from the user when an opaque task has downstream consumers.",
"_opaque_trusted": {
"$type": "string ('true' or 'false')",
"$effect": "When 'true', suppresses W172 across all downstream references to this opaque task's effective scope.",
"$example": "{ \"_opaque_trusted\": \"true\" }"
},
"_expected_response_schema": {
"$type": "object keyed by the opaque task's effective scope, e.g. { 'ExternalApi': { 'acknowledgmentId': 'string', 'errors': [{...}] } }",
"$effect": "Documents the expected response shape. The linter then performs field-level checks on Data.<scope>.<field> references for that scope as if it were deterministic.",
"$example": "{ \"_expected_response_schema\": { \"ExternalApi\": { \"acknowledgmentId\": \"string\", \"receivedAt\": \"string\" } } }"
}
},
"default_data_workflow_keys": {
"$description": "Keys always present under Data.Workflow at task #1 of every workflow, regardless of trigger style. Sourced exclusively from Workflow::Instance#set_data (instance.rb:115-157). The seed walker (lint-workflow-json.js -> computeAvailableScopes) starts with these.",
"Workflow": ["ExecutionDate", "ExecutionDateTime", "ExecutionDateTimeUTC", "WorkflowRunUser"],
"$notes": [
"LastRunDate / LastRunDateTime / LastRunDateTimeUTC are set conditionally at runtime (only when a previous run exists). They are NOT seeded here to avoid false positives when no prior run exists.",
"WorkflowSetup.* (WorkflowSetup.name, WorkflowSetup.description, etc.) is a separate Liquid scope — do not confuse with Data.Workflow.*."
]
},
"trigger_seeding_rules": {
"$description": "Per-trigger Data.* seeding the walker uses to populate the initial available_scopes at task #1. See workflow-data-flow.md section 3 for full narrative.",
"event_trigger": {
"seeds": "Data.Workflow.* + Data.<EventObject>.<key> for every entry in workflow.parameters.event_parameters[]",
"$note": "Only the keys explicitly listed in event_parameters get seeded - the full event payload does NOT come through automatically."
},
"callout_trigger": {
"seeds": "Data.Workflow.* + the inbound HTTP body merged at the configured root key (default Data.Callout)",
"$note": "Treated as OPAQUE by the linter at the workflow envelope level. Declare _expected_response_schema on workflow.parameters or _opaque_trusted='true' to enable downstream field-level validation."
},
"ondemand_trigger": {
"seeds": "Data.Workflow.* + Data.<Object>.<field> for every entry in workflow.parameters.fields[]",
"$note": "fields[] are user-supplied at launch time. Ordinary workflow inputs should use object_name 'Workflow'; use 'Files' for file uploads or a real supported dropdown object."
},
"scheduled_trigger": {
"seeds": "Data.Workflow.* + Data.<Object>.<field> for every default in workflow.parameters.fields[]",
"$note": "Same as ondemand but values come from defaults (no user prompt). Ordinary workflow inputs should use object_name 'Workflow'; use 'Files' for file uploads or a real supported dropdown object."
}
},
"data_flow_lint_rules": {
"$description": "Linter rule IDs added by the data-flow analysis layer. Implemented in scripts/lint-workflow-json.js. See workflow-data-flow.md section 12 for examples.",
"E170": {
"$severity": "error",
"$rule": "Data.<scope> referenced where <scope> is not in availableScopes for the current task. Suggest closest match if one exists."
},
"W171": {
"$severity": "warning",
"$rule": "Data.<scope>.<field> referenced where <scope> is in available DETERMINISTIC scopes but <field> is not in the producer's known field set. Downgraded to a notice when the producer's contract has fields_partial_known=true."
},
"W172": {
"$severity": "warning",
"$rule": "Data.<scope>.<x> referenced where the producing task is OPAQUE AND has not been tagged _opaque_trusted='true' AND has no _expected_response_schema. Build skill normally prompts for this in Step 3e; the linter catches lint-only runs."
},
"W173": {
"$severity": "warning",
"$rule": "Data.<scope> referenced inside an Iterate For-Each body where <scope> matches the Iterate's parameters.object - inside the body the scope is a single Hash, not an Array, so bracket-index forms like Data.<scope>[0].<field> won't resolve."
},
"W174": {
"$severity": "warning",
"$rule": "Data.<scope> referenced from a task downstream of a Logic::Merge whose source Logic::Case only produces <scope> on some branches. Reference may resolve to nothing on the other branches."
}
},
"canonical_linkage_types": {
"$description": "Exact spellings the composer must emit. Anything else triggers a linter typo hint.",
"start": "Start",
"default": ["Success", "Failure"],
"if": ["True", "False", "Failure"],
"iterate": ["For Each", "Complete", "Failure"],
"logic_case": {
"$pattern": "Case_<N>",
"else": "Case_Else",
"failure": "Failure",
"$note": "Composer pre-normalizes parameters.case_condition to sequential keys Case_1, Case_2, ..., Case_Else BEFORE emitting linkages, so Rails Logic::Case#before_save :validate_labels is a no-op (app/models/tasks/logic/case.rb:51-73)."
},
"approval": ["Approve", "Reject", "Failure"],
"ui_page": {
"$pattern": "Page:<route>",
"$note": "One hook per non-blank parameters.route entry (app/models/tasks/ui/page.rb:20-24). Also includes Success and Failure from the base class."
},
"ui_web_share": {
"$pattern": "Webshare:<route>",
"plus": ["Upload", "Timeout", "Success", "Failure"],
"$note": "Note Webshare capitalization. From app/models/tasks/ui/web_share.rb:14-17, :36-38."
},
"usage_mediation": {
"$values": ["next", "error"],
"$note": "Lowercase. Differs from the standard Capitalized hooks on all other task types."
}
},
"typo_hints": {
"$description": "Linter surfaces these as 'did you mean' suggestions. Rails does not validate linkage_type strings on save; we are the only guard.",
"Iterate": "For Each",
"ForEach": "For Each",
"for_each": "For Each",
"for each": "For Each",
"case_1": "Case_1",
"case1": "Case_1",
"Case 1": "Case_1",
"case_else": "Case_Else",
"CaseElse": "Case_Else",
"success": "Success",
"SUCCESS": "Success",
"failure": "Failure",
"FAILURE": "Failure",
"true": "True",
"false": "False",
"start": "Start",
"STARTING": "Start"
},
"special_rules": {
"workflow_type_value": "Workflow::Setup",
"css_pixel_suffix": "px",
"start_linkage_required": true,
"forbid_for_each_before_merge": true,
"require_non_empty_tasks_and_linkages": true,
"require_parameters_object_per_task": true,
"boolean_params_must_be_strings": true,
"$notes": [
"workflow_type_value: Rails overrides whatever the client sends for workflow.type, but the composer still emits the canonical value so the JSON matches Workflow::Setup#export output. See app/models/workflow/setup.rb:418.",
"start_linkage_required: Workflow::Setup.import requires exactly one linkage with linkage_type 'Start' whose source_workflow_id == workflow.id and source_task_id == null.",
"forbid_for_each_before_merge: mirrors app/models/linkage.rb:72-122 avoid_for_each_linkage_before_merge_task. Path-substring check is sufficient client-side; server DFS + merge_start/merge_paths rewrites are server-only.",
"require_non_empty_tasks_and_linkages: Workflow::Setup.import raises 'No tasks/linkages present in import payload' when either array is blank (setup.rb:411-413).",
"require_parameters_object_per_task: Task.import uses parameters.merge! which raises on nil (task.rb:1652-1701). Every emitted task needs 'parameters': {} minimum.",
"boolean_params_must_be_strings: many task params are read with .to_bool from parameters, e.g. strict_variables, disable_validation. Always emit the string 'true' or 'false', never JSON true/false."
]
},
"required_at_import_catalog": {
"$description": "Top-level task attributes validated at import time by ActiveRecord column presence rules (NOT task_setup_validation). Distilled from tasks/*.rb validates :column, presence: true directives.",
"object": [
"Create",
"Query",
"Export",
"Update",
"Data::Aqua",
"Iterate",
"Logic::XMLTransform",
"File::ZuoraImport",
"File::FileOperations",
"File::FileStreamingUpload",
"File::BulkDataLoader",
"Usage::ImportUsage",
"Attachment"
],
"object_id": [
"Update",
"Delete",
"NewProduct",
"RemoveProduct",
"Suspend",
"Resume",
"Cancel",
"Attachment",
"InvoiceGenerate",
"WriteOff",
"Billing::ReverseInvoice",
"Billing::CustomBillingDocument",
"Reporting::RunReport",
"CustomObject::Update",
"CustomObject::Delete"
],
"$note": "These AR column validations DO fire on Task.import even though task_setup_validation is skipped. Their value may be a literal (e.g., 'Invoice') or a Liquid reference (e.g., '{{Data.Subscription.Id}}')."
},
"linkage_template": {
"$description": "Shape of one linkage entry. source_workflow_id is non-null only on Start (and other workflow-sourced) linkages; source_task_id is non-null for every task->task edge.",
"source_workflow_id": null,
"source_task_id": null,
"target_task_id": null,
"linkage_type": ""
},
"css_layout_defaults": {
"$description": "Canvas coordinates used by the composer so imported workflows render legibly in the UI.",
"workflow_origin": { "top": "40px", "left": "35px" },
"first_task": { "top": "40px", "left": "350px" },
"success_step_delta": { "left": 400 },
"branch_stack_delta": { "top": 120 }
}
}
SHA-256: f8b7fa0fff694d9c6b340656f9bcc28a9c9e64a8ffe5d708ebf9b33b5780b19f