@datarails-ai/dr-cli 0.0.1-locked → 1.6.70

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 (58) hide show
  1. package/README.md +608 -0
  2. package/dist/chunk-DWMCBGQH.js +242 -0
  3. package/dist/cli.js +64468 -0
  4. package/dist/kb-6UWI7Q32.js +21 -0
  5. package/dist/scopes-map.json +4412 -0
  6. package/dist/skills/connector-sandbox/SKILL.md +95 -0
  7. package/dist/skills/datarails-connect-workato/SKILL.md +140 -0
  8. package/dist/skills/datarails-connectors-onboard-connector/SKILL.md +813 -0
  9. package/dist/skills/dr-cli/SKILL.md +659 -0
  10. package/dist/skills/dr-cli/reference/admin.md +796 -0
  11. package/dist/skills/dr-cli/reference/budget-builder.md +151 -0
  12. package/dist/skills/dr-cli/reference/calc-vals.md +73 -0
  13. package/dist/skills/dr-cli/reference/calcmodels.md +76 -0
  14. package/dist/skills/dr-cli/reference/cash.md +61 -0
  15. package/dist/skills/dr-cli/reference/dashboards-widgets.md +85 -0
  16. package/dist/skills/dr-cli/reference/datamappers.md +150 -0
  17. package/dist/skills/dr-cli/reference/desk.md +472 -0
  18. package/dist/skills/dr-cli/reference/filebox-documents.md +373 -0
  19. package/dist/skills/dr-cli/reference/forecast.md +51 -0
  20. package/dist/skills/dr-cli/reference/integrations.md +57 -0
  21. package/dist/skills/dr-cli/reference/lookups.md +168 -0
  22. package/dist/skills/dr-cli/reference/metrics.md +512 -0
  23. package/dist/skills/dr-cli/reference/multi-product.md +180 -0
  24. package/dist/skills/dr-cli/reference/reconciliation-mec.md +76 -0
  25. package/dist/skills/dr-cli/reference/rh-config.md +84 -0
  26. package/dist/skills/dr-cli/reference/safety.md +143 -0
  27. package/dist/skills/dr-cli/reference/spend-control.md +206 -0
  28. package/dist/skills/dr-cli/reference/templates.md +163 -0
  29. package/dist/skills/dr-cli/reference/workflows.md +137 -0
  30. package/dist/skills/dr-error-enrichment/SKILL.md +80 -0
  31. package/dist/skills/dr-error-enrichment/reference/core.md +257 -0
  32. package/dist/skills/dr-error-enrichment/reference/groundcover.md +84 -0
  33. package/dist/skills/dr-error-enrichment/reference/jira-bugs.md +219 -0
  34. package/dist/skills/dr-error-enrichment/reference/safety-backup.md +122 -0
  35. package/dist/skills/dr-error-enrichment-author/SKILL.md +219 -0
  36. package/dist/skills/dr-error-enrichment-improve/SKILL.md +218 -0
  37. package/dist/skills/dr-error-enrichment-maintain/SKILL.md +237 -0
  38. package/dist/skills/dr-error-enrichment-reconcile/SKILL.md +167 -0
  39. package/dist/skills/dr-filebox-api-setup/SKILL.md +170 -0
  40. package/dist/skills/dr-switch-user/SKILL.md +395 -0
  41. package/dist/skills/dr-validate/SKILL.md +392 -0
  42. package/dist/skills/dr-validate/report-template.html +950 -0
  43. package/dist/skills/fx-conversion-matrix/SKILL.md +763 -0
  44. package/dist/skills/fx-conversion-matrix/scripts/_args.mjs +52 -0
  45. package/dist/skills/fx-conversion-matrix/scripts/_xlsx.mjs +241 -0
  46. package/dist/skills/fx-conversion-matrix/scripts/build-matrix.mjs +366 -0
  47. package/dist/skills/fx-conversion-matrix/scripts/gen-mappers.mjs +171 -0
  48. package/dist/skills/fx-conversion-matrix/scripts/rates-to-xlsx.mjs +172 -0
  49. package/dist/skills/fx-onboard/SKILL.md +325 -0
  50. package/dist/skills/mec-table-validation/SKILL.md +383 -0
  51. package/dist/skills/plugins.json +24 -0
  52. package/dist/skills/validate-workato-migration/SKILL.md +167 -0
  53. package/dist/skills/validate-workato-migration/reference/gotchas.md +16 -0
  54. package/dist/skills/validate-workato-migration/reference/staging-orgs.md +26 -0
  55. package/dist/skills/validate-workato-migration/scripts/compare_files.py +255 -0
  56. package/dist/zendeskKb-JPSHVOPH.js +243 -0
  57. package/package.json +56 -9
  58. package/scripts/postinstall.mjs +91 -0
@@ -0,0 +1,383 @@
1
+ ---
2
+ name: mec-table-validation
3
+ description: Read-only audit of a Datarails tenant's MEC `trial_balance` table against UDM standards via dr-cli. Runs 8 structural and data-quality checks (table existence, mandatory aliases, types, population, currency uniqueness, rate uniqueness, reporting-month shape) and emits a pass/fail report. Use whenever the user wants to verify, audit, debug, or sanity-check an MEC environment, trial_balance setup, or asks "is this tenant set up correctly?" — even when they don't say "mec-table-validation" by name. Trigger especially when a user is investigating why MEC reconciliation, FX translation, or close numbers look wrong on a tenant — config drift on the TB table is the most common root cause and this skill surfaces it in seconds before deeper debugging starts. Also trigger on phrases like "check trial_balance standards", "is MEC compliant", "audit MEC config", "MEC sanity check", "TB structure check".
4
+ ---
5
+
6
+ # mec-table-validation — Datarails MEC trial_balance verifier
7
+
8
+ ## What this skill does
9
+
10
+ Read-only audit of a Datarails tenant's MEC `trial_balance` table
11
+ against UDM standards. Runs 8 checks via `dr-cli` and emits a pass/fail
12
+ report.
13
+
14
+ The point is **fast triage**: when MEC reconciliation, FX translation,
15
+ or close numbers look wrong on a tenant, config drift on the TB table is
16
+ the most common root cause. Running this first surfaces the broken
17
+ config before the operator wastes time debugging downstream symptoms.
18
+
19
+ ## When to use
20
+
21
+ - "Verify MEC setup on this tenant"
22
+ - "Is this tenant's MEC compliant?"
23
+ - "Audit trial_balance"
24
+ - "Check MEC standards"
25
+ - "Why is MEC reconciliation broken on this tenant?" (run this before
26
+ deep debugging)
27
+ - "Sanity-check TB structure"
28
+ - "Debug MEC config"
29
+
30
+ ## Prerequisites
31
+
32
+ - `dr` CLI installed and on PATH (see `~/.claude/skills/dr-cli/SKILL.md`).
33
+ - Logged in to the target server (`dr login --server <key>`); JWT
34
+ refresh is automatic so an expired access token is fine.
35
+ - Operator is impersonated as the right tenant. The skill verifies
36
+ whoever is currently impersonated — the pre-flight `whoami` exists so
37
+ you can confirm before running.
38
+
39
+ ## Inputs
40
+
41
+ - `--server <key>` — required. Valid: `US`, `US2`, `UK`, `CA`, `EU`, `DEV`,
42
+ `DEV-1`, `TEST`, `TEST-1`, `TEST-2`, `TEST-3`, `TEST-4`, `TEST-5`,
43
+ `TEST-6`, `TEST-7`, `DEMO` (case-insensitive). Default per project
44
+ convention: `US`.
45
+ - `--report` — optional. Writes a markdown report to cwd in addition to
46
+ terminal output (filename: `mec-table-validation-<orgId>-<YYYY-MM-DD>.md`).
47
+ - `--json` — optional. Emits structured JSON instead of the human
48
+ report. Use when piping or scripting.
49
+
50
+ That's it. The skill runs against whoever is currently impersonated on
51
+ that server.
52
+
53
+ ## Pre-flight (always)
54
+
55
+ Run first, before any check, and echo the result to the user:
56
+
57
+ ```
58
+ dr whoami --server <key>
59
+ ```
60
+
61
+ This decodes the local JWT (no network call) and reveals who you'll be
62
+ auditing. Auditing the wrong tenant is the only way this skill produces
63
+ misleading-but-confident results, so this step is non-negotiable. Do
64
+ not proceed if the user hasn't confirmed it's the right tenant.
65
+
66
+ ## The standard
67
+
68
+ The 8 checks below are the source of truth. They derive from the
69
+ "Month End Close (MEC) UDM Data Scheme Set up Instructions"
70
+ Confluence page (id `1467482113`) plus operator-confirmed conventions
71
+ that aren't in that doc.
72
+
73
+ ### Mandatory field aliases + expected types
74
+
75
+ The MEC TB table must expose these 8 fields by their **system alias**
76
+ (not display name). Aliases follow "lowercase, spaces → `_`"
77
+ EXCEPT for the Account Group L0/L1/L2/L3 series, which use the
78
+ UDM-standard `dr_acc_lN` aliases — that's a known and documented
79
+ convention exception, not a tenant misconfiguration.
80
+
81
+ | Required alias | Expected type | Notes |
82
+ |---------------------------------------:|:-----------------|:------|
83
+ | `account_full` | Text | account number + name |
84
+ | `posting_amount` | Float | balance in posting currency |
85
+ | `entity` | Text | |
86
+ | `posting_currency` | Text | local currency, e.g. `USD`/`GBP`/`EUR` |
87
+ | `reporting_month` | Date | epoch on the wire |
88
+ | `reporting_rate` | Text **or** Float | either accepted — Text is legitimate |
89
+ | L3 grouping field (see below) | Text | |
90
+ | `reporting_currency` | Text | consolidation target |
91
+
92
+ **L3 alias resolution.** The L3 grouping field can ship under either
93
+ of two aliases. Resolve in this order:
94
+
95
+ 1. If `mec_grouping` is set on a field, use it. (Newer MEC convention.)
96
+ 2. Else if `dr_acc_l3` is set, use that.
97
+ 3. If neither alias appears in `fields[]`, check 2 fails for L3 and
98
+ any data check that depends on L3 is reported as `skip` with a note
99
+ explaining why.
100
+
101
+ ### The 8 checks
102
+
103
+ 1. **TB table exists.** `dr semantictables get trial_balance` returns
104
+ a valid response.
105
+ 2. **All 8 mandatory aliases present.** Walk `fields[]` from check 1
106
+ and confirm every required alias appears (using the L3 resolution
107
+ rule above).
108
+ 3. **Each mandatory field has the expected type.** Same response;
109
+ compare each field's `type` to the table.
110
+ 4. **Each mandatory field is populated.** For each alias, distinct
111
+ values across the table is non-empty. (A field can exist with the
112
+ right type but be entirely null on this tenant — that's a real
113
+ misconfig.)
114
+ 5. **Each `entity` → exactly one `posting_currency`.** A given entity
115
+ should not change posting currency across rows; if it does, FX
116
+ translation will silently produce nonsense.
117
+ 6. **Whole table → exactly one `reporting_currency`.** Reporting
118
+ currency is the single consolidation target for the org; multiple
119
+ distinct values means the FX translation chain is broken or the
120
+ table has been mis-fed.
121
+ 7. **Each `(account_full, reporting_month, entity)` → exactly one
122
+ `reporting_rate`.** Same account, same month, same entity should
123
+ resolve to one rate. Multiple rates means the FX-rate join landed
124
+ on multiple matches.
125
+ 8. **`reporting_month` is always last-day-of-month at 00:00:00 UTC.**
126
+ Wire format is unix-seconds epoch. Each distinct value must decode
127
+ to the last calendar day of its month (28/29/30/31 depending on
128
+ month and leap year) with no intra-day component.
129
+
130
+ ## Recipe — how to execute
131
+
132
+ Sequence matters. Checks 1–3 read metadata once and feed checks 4–8.
133
+
134
+ ### Step 1 — identify tenant
135
+
136
+ ```
137
+ dr whoami --server <key>
138
+ ```
139
+
140
+ Echo the resolved server, email, user_id to the user. Do not proceed
141
+ unless the user has confirmed (or it's already established) that this
142
+ is the tenant they want to audit.
143
+
144
+ ### Step 2 — fetch trial_balance metadata
145
+
146
+ ```
147
+ dr semantictables get trial_balance --server <key> --json
148
+ ```
149
+
150
+ If this fails (404, no such alias, error response), check 1 fails and
151
+ the rest of the run is skipped — there's nothing to audit.
152
+
153
+ From the response, capture:
154
+
155
+ - `id` — needed for `templates aggregate` calls (which take a numeric
156
+ template id, not an alias).
157
+ - `fields[]` — for alias presence + type checks.
158
+ - An **alias → name map** built from `fields[]` — `templates aggregate`
159
+ takes display field NAMES (e.g. `Entity`, `Posting Currency`), not
160
+ aliases. Without this map the aggregate calls won't resolve. (This is
161
+ the one asymmetry between the two CLI surfaces.)
162
+
163
+ ### Step 3 — resolve the L3 alias
164
+
165
+ Look in `fields[]` for `mec_grouping`. If present, use it. Else look
166
+ for `dr_acc_l3`. If neither, mark check 2 as fail for L3 and `skip`
167
+ any L3-dependent data check.
168
+
169
+ ### Step 4 — checks 2 + 3 (alias + type, in-memory)
170
+
171
+ Walk the required-aliases table and verify each appears in `fields[]`
172
+ with the expected type. No additional CLI calls — both checks come from
173
+ the response captured in Step 2.
174
+
175
+ For `reporting_rate`, accept either `Text` or `Float`. For all other
176
+ mandatory fields, the type must match exactly.
177
+
178
+ ### Step 5 — check 4 (population)
179
+
180
+ For each mandatory alias that exists on this tenant:
181
+
182
+ ```
183
+ dr semantictables distinct trial_balance --field <alias> --server <key> --json
184
+ ```
185
+
186
+ Pass if the returned array has length ≥ 1. Fail (with the empty result)
187
+ if the column has no non-null values across the table.
188
+
189
+ ### Step 6 — check 6 (single reporting_currency)
190
+
191
+ ```
192
+ dr semantictables distinct trial_balance --field reporting_currency --server <key> --json
193
+ ```
194
+
195
+ Pass if length = 1. On fail, surface the distinct values found.
196
+
197
+ ### Step 7 — check 5 (entity → one posting_currency)
198
+
199
+ Use the alias→name map from Step 2 and the template id captured there:
200
+
201
+ ```
202
+ dr templates aggregate <id> \
203
+ --group-by <name_of_entity_field> \
204
+ --metric "<name_of_posting_currency_field>:COUNT_UNIQUE" \
205
+ --server <key> --json
206
+ ```
207
+
208
+ The response is a JSON array containing **three kinds of entries**, in
209
+ this order:
210
+
211
+ ```json
212
+ [
213
+ {"<entity_name>": "FR01", "<posting_currency_name>": 1},
214
+ {"<entity_name>": "UK01", "<posting_currency_name>": 1},
215
+ {"<entity_name>": "DE01", "<posting_currency_name>": 1},
216
+ {"<posting_currency_name>": 5}, // totals row — skip
217
+ {"col_keys": [], "row_keys": [["DE01"], ...]} // envelope — skip
218
+ ]
219
+ ```
220
+
221
+ Filter to entries that have the entity field set (per-group rows).
222
+ For each such row, the metric column must equal 1. Surface any entity
223
+ with count > 1 as a fail.
224
+
225
+ ### Step 8 — check 7 (rate uniqueness)
226
+
227
+ ```
228
+ dr templates aggregate <id> \
229
+ --group-by <account_full_name> \
230
+ --group-by <reporting_month_name> \
231
+ --group-by <entity_name> \
232
+ --metric "<reporting_rate_name>:COUNT_UNIQUE" \
233
+ --server <key> --json
234
+ ```
235
+
236
+ Same response shape — filter to per-group rows, check the metric
237
+ column is 1 for each. Surface up to 10 violating tuples on fail (don't
238
+ dump thousands of rows).
239
+
240
+ ### Step 9 — check 8 (reporting_month shape)
241
+
242
+ ```
243
+ dr semantictables distinct trial_balance --field reporting_month --server <key> --json
244
+ ```
245
+
246
+ Returns an array of unix-seconds epochs (as JSON strings). For each:
247
+
248
+ - Decode to UTC date.
249
+ - Verify time component is 00:00:00 UTC.
250
+ - Verify the day-of-month equals the last calendar day of that month
251
+ (28/29 for Feb, 30 for Apr/Jun/Sep/Nov, 31 for the rest; account for
252
+ leap years on Feb).
253
+
254
+ Pass if every epoch decodes cleanly to last-of-month at midnight UTC.
255
+ Fail otherwise; list each offending epoch with its decoded date so the
256
+ operator can spot whether it's an off-by-one (first-of-month was
257
+ configured) or a different shape entirely.
258
+
259
+ ### Step 10 — render report
260
+
261
+ After all 8 checks have run, render the report (terminal default;
262
+ `--json` for structured; `--report` for markdown to disk).
263
+
264
+ ## Output format
265
+
266
+ ### Default (human-readable terminal)
267
+
268
+ Open with the tenant block, then one line per check, then a summary.
269
+ Failures expand under the check line with details.
270
+
271
+ ```
272
+ Server: US (https://app.datarails.com)
273
+ Email: support@mec-demo.com
274
+ User ID: 73825
275
+ Table: trial_balance (id 36928, name "TB")
276
+
277
+ Check 1 ✓ trial_balance table exists
278
+ Check 2 ✓ all 8 mandatory aliases present
279
+ L3 resolved to: dr_acc_l3
280
+ Check 3 ✓ all field types match
281
+ Check 4 ✓ all fields populated
282
+ Check 5 ✓ every entity → 1 posting_currency
283
+ Check 6 ✓ whole table → 1 reporting_currency (USD)
284
+ Check 7 ✓ reporting_rate unique per (account_full, reporting_month, entity)
285
+ Check 8 ✓ reporting_month always last-of-month UTC
286
+
287
+ Result: 8/8 checks pass
288
+ ```
289
+
290
+ On any fail:
291
+
292
+ ```
293
+ Check 3 ✗ field type mismatch
294
+ posting_amount expected Float, got Text
295
+
296
+ Check 5 ✗ entity → multiple posting_currency
297
+ DE01 has 2 distinct posting_currency values
298
+ FR01 has 3 distinct posting_currency values
299
+
300
+ Check 8 ✗ reporting_month is not last-of-month UTC
301
+ 1738368000 → 2025-02-01 00:00:00 UTC (first-of-month, not last)
302
+ 1740787200 → 2025-03-01 00:00:00 UTC (first-of-month, not last)
303
+ ```
304
+
305
+ ### `--json` flag
306
+
307
+ Emit a structured object for scripting. Status enum:
308
+ `pass | fail | warn | skip | error`.
309
+
310
+ ```json
311
+ {
312
+ "tenant": {
313
+ "server": "US",
314
+ "email": "support@mec-demo.com",
315
+ "user_id": 73825
316
+ },
317
+ "table": {"id": 36928, "name": "TB", "alias": "trial_balance"},
318
+ "l3_alias_resolved_to": "dr_acc_l3",
319
+ "checks": [
320
+ {"id": 1, "name": "trial_balance exists", "status": "pass"},
321
+ {"id": 2, "name": "mandatory aliases present", "status": "pass"},
322
+ {"id": 3, "name": "field types", "status": "fail",
323
+ "details": [{"alias": "posting_amount", "expected": "Float", "actual": "Text"}]}
324
+ ],
325
+ "summary": {"pass": 7, "fail": 1, "warn": 0, "skip": 0, "error": 0, "total": 8}
326
+ }
327
+ ```
328
+
329
+ ### `--report` flag (markdown report on disk)
330
+
331
+ Writes `mec-table-validation-<orgId>-<YYYY-MM-DD>.md` to cwd. Same content as
332
+ the terminal output plus a metadata block at the top, matching the
333
+ project's report-naming convention:
334
+
335
+ ```
336
+ **Date:** YYYY-MM-DD
337
+ **Operator:** <email running the audit>
338
+ **Server:** <key>
339
+ **Authenticated as:** <impersonated email> (user_id N)
340
+ **CLI version:** dr-cli X.Y.Z
341
+ **Tool:** mec-table-validation
342
+ ```
343
+
344
+ ## Edge cases
345
+
346
+ | Situation | Behavior |
347
+ |---|---|
348
+ | TB table missing (semantictables get fails) | Check 1 fails; checks 2–8 are reported as `skip` with note "no trial_balance to audit". Exit early. |
349
+ | Required alias missing | Check 2 fails for that alias. Data checks that depend on the missing field (e.g. check 7 needs `reporting_rate`) are `skip` with note "depends on missing alias <X>". |
350
+ | TB exists but is empty (zero rows) | Checks 4–8 are `warn`, not `fail`. A fresh tenant pre-rescan can legitimately have zero rows — flagging this as `fail` would cry wolf. |
351
+ | `templates aggregate` 500 / timeout | Default 60s polling budget can be too short for large tenants. Re-run that single check with `--timeout 180000`. If it still fails, surface the error verbatim and mark the check `error`. |
352
+ | L3 has both `mec_grouping` and `dr_acc_l3` set | Use `mec_grouping` (newer convention). Note this in the report so the operator knows which alias the audit used. |
353
+ | `reporting_rate` field type is Text | Pass check 3 — Text is documented as legitimate (rate is sometimes stored as a formula expression). Float also passes. Anything else (Integer, Boolean) fails. |
354
+
355
+ ## Guard rails
356
+
357
+ - **Read-only.** This skill never writes. No confirmation gates needed.
358
+ - **Always pass `--json`** to every `dr` call. Table output is for
359
+ humans; the recipe parses everything programmatically and table
360
+ formatting will silently truncate.
361
+ - **Never paste credentials.** Session tokens live in the OS keychain;
362
+ JWTs and session IDs are off-limits per project convention.
363
+ - **Always run `whoami` first.** Auditing the wrong tenant produces
364
+ confidently-wrong findings — the worst kind. Two seconds of
365
+ pre-flight prevents this.
366
+
367
+ ## Common mistakes
368
+
369
+ | Mistake | Fix |
370
+ |---|---|
371
+ | Using aliases in `templates aggregate --group-by` | aggregate takes display field NAMES (`Entity`, not `entity`); resolve via the alias→name map captured from `semantictables get` in Step 2 |
372
+ | Treating `reporting_month` as ISO date | Wire format is unix-seconds epoch (as a JSON string). Decode before comparing. |
373
+ | Reading the totals or envelope row as a per-entity row | `templates aggregate` returns per-group rows + a totals row + a `{col_keys, row_keys}` envelope. Filter to entries that have the group-by field set before checking metric values. |
374
+ | Failing the run because TB has zero rows | Empty TB on a fresh tenant is `warn`, not `fail` — let the operator know without crying wolf. |
375
+ | Re-running `dr login` because `whoami` shows JWT expired | Don't. JWT refresh is automatic on the next API call. Re-login only after a real `session expired` error post-call. |
376
+
377
+ ## Related skills
378
+
379
+ - `~/.claude/skills/mec-implementation/SKILL.md` — sets up an MEC tenant
380
+ from scratch. If this verifier fails, that skill is what the operator
381
+ uses to fix it.
382
+ - `~/.claude/skills/dr-cli/SKILL.md` — the underlying CLI inventory.
383
+ All commands this skill issues are documented there.
@@ -0,0 +1,24 @@
1
+ {
2
+ "dr-cli-integrations": [
3
+ "connector-sandbox",
4
+ "datarails-connect-workato",
5
+ "datarails-connectors-onboard-connector",
6
+ "dr-error-enrichment",
7
+ "dr-error-enrichment-author",
8
+ "dr-error-enrichment-improve",
9
+ "dr-error-enrichment-maintain",
10
+ "dr-error-enrichment-reconcile",
11
+ "dr-filebox-api-setup",
12
+ "validate-workato-migration"
13
+ ],
14
+ "dr-cli": [
15
+ "dr-cli",
16
+ "dr-switch-user",
17
+ "fx-conversion-matrix",
18
+ "fx-onboard"
19
+ ],
20
+ "dr-cli-pat": [
21
+ "dr-validate",
22
+ "mec-table-validation"
23
+ ]
24
+ }
@@ -0,0 +1,167 @@
1
+ ---
2
+ name: validate-workato-migration
3
+ description: Use when a customer's Workato feed is being migrated to a dr-connectors (DRIP) connector and the connector's output has to be proven identical to Workato's before cut-over, or when working in the Workato-migration staging org test-workato-migration.com (US 21304, US2 1691, UK 3289, CA 5106). Also for "query create" returning 422 "artifact version not registered" for a staging-org binding, and for comparing connector CSVs with Workato filebox versions. Not for building or publishing the connector itself (datarails-connectors-onboard-connector) or for reading Workato alone (datarails-connect-workato). Triggers - "validate the migration", "compare Workato vs connector", "parity check against Workato", "test the migration in the staging org", "test_workato_migration org", "are the connector files identical to Workato", "migration validation report".
4
+ ---
5
+
6
+ # Validate a Workato → DRIP migration in the staging org
7
+
8
+ Validation never happens in the customer's org. Parallel `(migration)` fileboxes there auto-map
9
+ into the **same tables** as the Workato fileboxes and double-count the customer's data. It happens in a **dedicated staging org per server** instead, which has no tables. The
10
+ customer's org is only **read**. Cut-over is out of scope.
11
+
12
+ Both sides are compared as the **CSV stored in Datarails**: Workato's recipe upload in the customer
13
+ filebox, and the connector's landed version in the staging mirror (`dr filebox download
14
+ --doc-version` returns the stored bytes). Never compare tables/StarRocks (they come after the Data
15
+ Mapper and hide quoting/format differences), and never re-run a Workato recipe (that's a write).
16
+
17
+ ## Staging orgs (domain `test-workato-migration.com`)
18
+
19
+ | DRIP region folder | `--server` | Staging org id |
20
+ |---|---|---|
21
+ | `us/` | `US` | **21304** |
22
+ | `us2/` | `US2` | **1691** |
23
+ | `uk/` | `UK` | **3289** |
24
+ | `ca/` | `CA` | **5106** |
25
+
26
+ Identity: `support@test-workato-migration.com`. The customer and the staging org are always on the
27
+ same server, and a customer on another datacenter needs a new staging org and row first. One-time setup
28
+ and the access rules are in [reference/staging-orgs.md](reference/staging-orgs.md).
29
+
30
+ ## Rules (non-negotiable)
31
+
32
+ 1. **Prod.** All four servers are production. Show the user each write (folder/filebox create,
33
+ datasource, binding, sync, teardown) and get a yes first. Reads need no confirmation.
34
+ 2. **Two identities, never mixed up.** Every `dr` call names its `--account`:
35
+ `STG="--server <S> --account support@test-workato-migration.com"` for **all writes**;
36
+ `CUS="--server <S> --account <customer support user>"` for **reads only**. The org comes from the JWT
37
+ (`dr filebox create` has no `--org`), so the wrong account writes into the wrong org silently.
38
+ Before the first write, `dr whoami $STG` must print `Impersonated by:` and the staging org id.
39
+ 3. **Byte-identical is the only pass.** sha256 equal. Same values but different quoting,
40
+ `-1` vs `-1.0` or CRLF vs LF is a **FAIL**: the connector must reproduce Workato's bytes.
41
+ 4. **Stop on mismatch.** Report and wait for the user. Don't edit, republish or re-sync on your own.
42
+ 5. `dr connectors` calls need VPN + `NODE_EXTRA_CA_CERTS` (see `datarails-connectors-onboard-connector`).
43
+ 6. Workato is **read-only**: GET only, via `datarails-connect-workato`.
44
+ 7. **Secrets never printed.** Credentials go from their source straight into the payload file in
45
+ the same shell call, and the file is deleted afterwards.
46
+
47
+ ## Prerequisites
48
+
49
+ The connector is merged to `main` in dr-connectors, and CI has tagged and published
50
+ `<dc>/<customer_org>/<vendor>/<dq>/vN` to JFrog (onboard skill §1–2). Scratch dir
51
+ `<scratchpad>/<customer>-validate/` holds `inventory.json`, `mapping.json`, `wk/`, `st/`,
52
+ `manifest.json` and `report.md`.
53
+
54
+ ## 0. Identities
55
+
56
+ ```bash
57
+ dr login-as support@test-workato-migration.com --server <S> --as <operator-email>
58
+ dr login-as <customer support user> --server <S> --as <operator-email>
59
+ dr whoami $STG ; dr whoami $CUS
60
+ ```
61
+ Impersonation JWTs last ~5 minutes, so re-run `login-as` on a 401.
62
+
63
+ ## 1. Inventory from Workato (read-only)
64
+
65
+ Start from the **recipes**, not the customer's fileboxes (`datarails-connect-workato`):
66
+ 1. `GET /managed_users` → the customer's `managed_user_id`; `GET /managed_users/{id}/recipes` → agree with the user which recipes are **in scope**.
67
+ 2. Per recipe: the **target filebox** it uploads to (recipe code / triggering webhook) and the **dataset + selector** it pulls (e.g. report × restaurant GUID).
68
+ 3. **Credentials:** `account_property` pills resolve to environment properties, which the API returns (`GET /managed_users/{id}/properties?prefix=<p>`). Workato **connection** secrets aren't returned by the API; get those from the vendor admin or the customer. Record the source. One credential → one datasource.
69
+ 4. `$CUS`: `dr filebox get <fb> --json` (name, parent chain, date tag config) and `dr filebox data-query get <fb>`.
70
+
71
+ Save `inventory.json` (`recipe_id, recipe_name, workato_fb, name, folder_path, date_sub_type,
72
+ dataset, selector, credential_source`) and show it to the user before creating anything.
73
+
74
+ ## 2. Staging tree (`$STG`, confirm first)
75
+
76
+ ```
77
+ <customer org id>_<Customer name>/ e.g. 16127_Acurio International
78
+ <System>/ source system as Workato names it, e.g. Toast
79
+ <Workato sub-folders below System>/ e.g. Labor/Shifts
80
+ <Workato filebox, same name> e.g. Jarana Aventura
81
+ ```
82
+ Mirror the sub-folders too, because per-entity filebox names repeat across datasets. Reuse existing folders
83
+ (`dr filebox list $STG --json`), since a re-run must never duplicate. Create folders with `dr filebox create
84
+ --name <n> --folder [--parent <id>]`, and each mirror 1:1 with the same name, scenario and date sub-type
85
+ (`--date-sub-type …` or `--no-date`) under `--parent <deepest folder>`. Capture `id` + `uuid` from `--json`.
86
+ Save `mapping.json` (`workato_fb, staging_fb, staging_uuid, name, dataset, selector`).
87
+
88
+ ## 3. Make the artifact available to the staging org
89
+
90
+ Ingest and bindings are org-scoped: a staging binding can't use the customer's ingested
91
+ version, and `query create` returns **422**. So copy the same bytes, then ingest as the staging org:
92
+ ```bash
93
+ gh workflow run copy-artifact-to-staging.yml -R Datarails/dr-connectors \
94
+ -f datacenter=<dc> -f customer_org_id=<org> -f vendor=<vendor> -f dataquery=<dq> -f version=vN
95
+ dr connectors publish "<dc>/<staging_org>_(test-workato-migration.com)/<vendor>/dataqueries/<dq>.py" --tag vN $STG --json
96
+ ```
97
+ The workflow can only write under the four staging orgs and refuses different bytes. The publish path
98
+ must name the **staging** org (the CLI rejects a path/JWT org mismatch), and `--tag` is required because
99
+ there's no git tag for that path. With `--tag`, `publish` only parses the path and never reads the file, so the
100
+ staging-org path need not exist locally; never commit a staging-org folder. `409` = already ingested.
101
+
102
+ ## 4. Wire the connector (`$STG`, confirm first)
103
+
104
+ As in the onboard skill §3–4, but in the staging org:
105
+ - **Datasource** `DatarailsConnector`, `is_sync_service: true`, `organization: <staging org>`, with the
106
+ credential from §1. Name it `<Customer> <System> (validation)`.
107
+ - **Filebox:** `dr documents support-api set <fb> --on`, then `dr filebox data-query set <fb>
108
+ --payload-file dq.json`. Write `config` and `config.query` as **nested objects** (the CLI encodes them):
109
+ ```json
110
+ {"connection": <ds>, "sync_only_changes": false,
111
+ "config": {"type": null, "query": {"objectType": "<dataset>", "<selector>": "<value>",
112
+ "startDate": "{{ date_tag|format:'%Y-%m-%d' }}", "endDate": "{{ date_tag|format:'%Y-%m-%d' }}",
113
+ "fileName": "<Workato's landed file name without .csv>"}}}
114
+ ```
115
+ Use the production Sync-query keys, and **no** sync interval.
116
+ - **Binding:** `dr connectors connector create --secret-ref <ds>`, then `dr connectors query create
117
+ --connector-id <id> --filebox-uuid <uuid> --filebox-id <fb> --artifact-name <vendor>__<dq>
118
+ --artifact-version vN --runtime-flavor <flavor>`. A 422 here means §3 is missing for this `vN`.
119
+
120
+ ## 5. Periods (read-only, `$CUS`)
121
+
122
+ `dr filebox versions <workato_fb> --json` → the **latest 3–5 distinct date tags**, newest version per
123
+ tag. Prefer **closed** periods past the recipe's own re-sync window (e.g. re-syncs 5 days → pick days
124
+ ≥ 6 days old). **Undated fileboxes:** latest Workato version vs a staging sync now. Vendor data may have
125
+ moved, so a mismatch there **isn't conclusive**.
126
+
127
+ ## 6. Sync (`$STG`, confirm first)
128
+
129
+ `dr filebox data-query sync <staging_fb> --date YYYY-MM-DD`: one sync at a time per filebox (a
130
+ running sync holds the Singleton, so the next trigger is skipped). Wait for the version in `dr filebox versions
131
+ <staging_fb> --json` before the next one. Failures: completion verdict + Groundcover (onboard skill §5).
132
+
133
+ ## 7. Compare, report, stop
134
+
135
+ ```bash
136
+ dr filebox download <workato_fb> --doc-version <wk_v> --output wk/<fb>_<wk_v>.csv --force $CUS
137
+ dr filebox download <staging_fb> --doc-version <st_v> --output st/<fb>_<st_v>.csv --force $STG
138
+ python <this skill's dir>/scripts/compare_files.py <scratch>/manifest.json --report <scratch>/report.md
139
+ ```
140
+ `manifest.json` sits next to `wk/`/`st/` and has one entry per Workato version: `name`
141
+ (`<System> / <filebox> / <period>`), `workato`, `workato_version`, `staging`, `staging_version`. Relative
142
+ paths resolve against the manifest. Exit 0 = at least one pair and all byte-identical. Otherwise each `DIFFERENT` pair is
143
+ diagnosed: encoding/BOM, line endings, trailing newline, header, rows only on one side, row order,
144
+ per-column cell diffs, quoting only, or unparseable.
145
+
146
+ Give the user `report.md` (every filebox × period, both version ids, sizes, sha256), the landed file
147
+ names on both sides, your read of each failure's likely cause (e.g. Workato quotes empty cells, or a
148
+ Workato recipe bug the user must decide whether to replicate), and the staging ids created. **Stop.** A fix
149
+ goes merge → new vN → §3 → `dr connectors query repoint` → re-sync. Cut-over is a separate, approved step.
150
+
151
+ ## 8. Teardown (once accepted, `$STG`, confirm first)
152
+
153
+ The staging datasource holds **live customer credentials**, so remove it right away:
154
+ 1. `dr connectors query list` → `dr connectors query delete <queryId>` for each binding.
155
+ 2. `dr integrations delete <ds>` (two-step confirm). dr-cli has no connector delete, so the orphaned
156
+ `Connector` row points at a deleted datasource. Record its id.
157
+ 3. Keep the staging fileboxes as evidence until cut-over + ticket close, then delete the `<Customer>` tree.
158
+ 4. Delete the local `wk/` and `st/` downloads: they are customer data. Keep only `report.md`.
159
+ 5. Record the report, what was deleted and what was kept in the migration ticket.
160
+
161
+ Symptoms and fixes: [reference/gotchas.md](reference/gotchas.md).
162
+
163
+ ## Related
164
+
165
+ - `datarails-connectors-onboard-connector`: build, publish and wire the connector.
166
+ - `datarails-connect-workato`: recipes, connections, properties (GET only).
167
+ - `dr-switch-user`: `dr login-as` details. `connector-sandbox`: local fix loop before a merge.
@@ -0,0 +1,16 @@
1
+ # Gotchas
2
+
3
+ | Symptom | Cause | Fix |
4
+ |---|---|---|
5
+ | Filebox/datasource appears in the customer org | A write ran with `$CUS` (or no `--account`) | Always pass `$STG` for writes; delete the stray object with the user's approval |
6
+ | `query create` → 422 "artifact version not registered" | The artifact is ingested only for the customer's org (the registry is org-scoped) | Step 3: `copy-artifact-to-staging.yml`, then `dr connectors publish <staging path> --tag vN $STG` |
7
+ | `dr connectors publish` aborts "published under org A … authenticated as org B" | Passed the customer's dataquery path with `$STG` | Use `<dc>/<staging_org>_(test-workato-migration.com)/…` + `--tag vN` |
8
+ | Copy workflow: "target exists with DIFFERENT bytes" | A different artifact was already copied for that `vN` | Stop and investigate. Never overwrite: the staging copy must equal what the customer runs |
9
+ | Second sync of a filebox never runs | The previous run still holds the filebox Singleton | Wait for the version to land before re-triggering |
10
+ | One row differs, on a recent period only | Vendor data changed after Workato's run | Re-pick older, closed periods before calling it a connector bug |
11
+ | Only bytes differ, values equal | Quoting / float formatting / line endings | Still a FAIL: match Workato's CSV writer in the connector |
12
+ | Sync-query shows escaped quotes / doesn't render | `config.query` written as a JSON string | Write it as a nested object; the CLI encodes it |
13
+ | `dr filebox get <new fb>` → "has no latest version" | The filebox is empty until the first sync | Harmless; use the `--json` output of `filebox create` |
14
+ | UI: fileboxes open by URL but the left panel is empty | support@ is ADMIN, not SUPER_ADMIN | [staging-orgs.md](staging-orgs.md) one-time setup |
15
+ | UI: "You don't have permission for this FileBox" | The browser is on a filebox from another org, or you're logged in as your own user | Log in as support@ and open `#/document_details/<staging id>` |
16
+ | 401 mid-run | Impersonation JWT (~5 min) expired | Re-run `dr login-as` for that identity |
@@ -0,0 +1,26 @@
1
+ # Staging orgs: setup and access
2
+
3
+ Created 2026-09-29 with `dr organizations add --domain test-workato-migration.com` on each server.
4
+ The server ignores `--name`, so each org is named after its domain.
5
+
6
+ | Server | Org id | support@ SUPER_ADMIN |
7
+ |---|---|---|
8
+ | US | 21304 | done 2026-09-29 |
9
+ | US2 | 1691 | not yet |
10
+ | UK | 3289 | not yet |
11
+ | CA | 5106 | not yet |
12
+
13
+ ## One-time setup per server
14
+
15
+ - **`support@test-workato-migration.com` must be SUPER_ADMIN** (admin Users screen). New orgs
16
+ create it as ADMIN, which can open fileboxes by URL but gets an **empty left-panel tree** in the UI.
17
+ Check: `dr organizations contacts --server <S> --account support@test-workato-migration.com --json`.
18
+ - The org's own `permissions` list is empty. That's expected: those entries are legacy feature flags.
19
+ - Everything created is shared only with support@. To look at it in the UI, log in as that user and
20
+ open `#/document_details/<id>`.
21
+
22
+ ## Access (proposed, pending sign-off)
23
+
24
+ Only operators running migrations work in these orgs, through `dr login-as` from their own operator
25
+ login. The orgs temporarily hold customer credentials and data, so don't add other users or share
26
+ anything from them. The skill's teardown step removes credentials after each validation.