@metabase/cli 0.3.1-alpha.support-worktrees.b634907 → 0.3.1-alpha.transform-tests.c0bc2d1
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.
- package/dist/{add-collection-CxzzDG4z.mjs → add-collection-CVDgC7C6.mjs} +5 -6
- package/dist/{alert-CQ7Az16h.mjs → alert-Co9m8k5l.mjs} +7 -7
- package/dist/{alerts-DmwyyBfW.mjs → alerts-DjZGGxOy.mjs} +6 -7
- package/dist/{append-B0avMkvW.mjs → append-BXqeNFzW.mjs} +3 -4
- package/dist/{archive-CMTHh4-w.mjs → archive-Bhk1IqGH.mjs} +3 -4
- package/dist/archive-CH9osnGq.mjs +32 -0
- package/dist/{archive-BQ50OZ7y.mjs → archive-CUGpIXDT.mjs} +3 -4
- package/dist/{archive-THcEfy9B.mjs → archive-CXYUkzZo.mjs} +4 -5
- package/dist/{archive-Cvrkz8F8.mjs → archive-Cchi-tqq.mjs} +4 -5
- package/dist/{archive-BPSNe0Ul.mjs → archive-Csku9zDp.mjs} +3 -4
- package/dist/{archive-BZEu6UfT.mjs → archive-CzcjKpUC.mjs} +5 -6
- package/dist/archive-D1xZRRRA.mjs +32 -0
- package/dist/{archive-DCyeRNfV.mjs → archive-DvV3oLCg.mjs} +3 -4
- package/dist/{archive-DFofUIEC.mjs → archive-I61vFPxv.mjs} +3 -4
- package/dist/{archive-BVblae-p.mjs → archive-pA5UZn1f.mjs} +3 -4
- package/dist/{auth-BEEkuu4J.mjs → auth-BKgMIAKH.mjs} +5 -5
- package/dist/{branches-CmWc8NyL.mjs → branches-B96keFsO.mjs} +3 -4
- package/dist/{cancel-DrQqmxvn.mjs → cancel-6GPBlUkr.mjs} +3 -4
- package/dist/cancel-task-BIGAgcKk.mjs +28 -0
- package/dist/{card-CrBGUkGQ.mjs → card-DZxRmver.mjs} +0 -1
- package/dist/card-zluRR7xD.mjs +24 -0
- package/dist/{card-BEeafd4Y.mjs → card-zyUkr2nS.mjs} +1 -1
- package/dist/{cards-M_6X8wCI.mjs → cards-DY6Togtl.mjs} +4 -5
- package/dist/cli.mjs +48 -142
- package/dist/{client-DYD8WZYy.mjs → client-C9HBWl6A.mjs} +129 -207
- package/dist/{collection-CJHqsFKP.mjs → collection-CGoVLvcD.mjs} +1 -1
- package/dist/{collection-BxE7OzcQ.mjs → collection-CfulNRBG.mjs} +3 -6
- package/dist/collection-CrIkQWUK.mjs +20 -0
- package/dist/{content-translation-C8MlXPOF.mjs → content-translation-Cg_VW3kW.mjs} +3 -3
- package/dist/{create-BqpkCjx4.mjs → create-BeNQQTPt.mjs} +2 -3
- package/dist/{create-BA_tTDEk.mjs → create-BkIoWkzP.mjs} +3 -4
- package/dist/{create-CJ7Am4Gc.mjs → create-BpjBffBq.mjs} +3 -4
- package/dist/create-BsDJATJM.mjs +34 -0
- package/dist/{create-CY-SzM66.mjs → create-BuWV1GpI2.mjs} +9 -14
- package/dist/{create-D0anrINz.mjs → create-BxWMTy6i.mjs} +8 -13
- package/dist/{create-ldrghMd1.mjs → create-CK5MABYu.mjs} +2 -3
- package/dist/{create-ByuJ6T4k.mjs → create-CaDAT54M.mjs} +5 -6
- package/dist/{create-z-oMrlfT.mjs → create-D_pNL2X9.mjs} +4 -5
- package/dist/{create-BLWwN0uw.mjs → create-DqCdW3tt.mjs} +3 -4
- package/dist/{create-BupBEwR2.mjs → create-JZevXtCI.mjs} +6 -11
- package/dist/{create-hTc131aX.mjs → create-V2Bdeu8p.mjs} +2 -3
- package/dist/{create-CoubLR5o.mjs → create-bH-qAv03.mjs} +3 -4
- package/dist/create-branch-DN9PrlRR.mjs +35 -0
- package/dist/{create-C7iooS6W.mjs → create-cHltWf0e.mjs} +7 -13
- package/dist/{create-myI6doga.mjs → create-cSpqEzKH.mjs} +3 -4
- package/dist/{create-C2XFfbjx.mjs → create-mzHtey5k.mjs} +2 -3
- package/dist/{csv-DBNzJiWK.mjs → csv-CVKLuIuS.mjs} +3 -4
- package/dist/{current-task-YzemU0Ic.mjs → current-task-Dnm3JaWg.mjs} +7 -12
- package/dist/{dashboard-B1mpfCq_.mjs → dashboard-SxUrG3Cg.mjs} +10 -10
- package/dist/{db-1tLH3nYI.mjs → db-BnhuXAP9.mjs} +7 -7
- package/dist/{delete-CpKH5ia4.mjs → delete-6lroLZIE.mjs} +4 -5
- package/dist/{delete-DjAUp9mk.mjs → delete-BClvnlBc.mjs} +4 -5
- package/dist/delete-C3xeqTzS.mjs +44 -0
- package/dist/{delete-eAUB7p-f.mjs → delete-CH0FsDBg.mjs} +5 -10
- package/dist/{delete-CD_-B4Kq.mjs → delete-DDVNawxh.mjs} +4 -5
- package/dist/{delete-QNgBV6eS.mjs → delete-Dn6VJuyR.mjs} +5 -10
- package/dist/{delete-runtime-b2_ZMSjj.mjs → delete-runtime-D74s6vhu.mjs} +1 -1
- package/dist/{delete-table-DamlxnSO.mjs → delete-table-C6LPzUDg.mjs} +5 -10
- package/dist/dependencies-vr51EFIu.mjs +36 -0
- package/dist/dirty-W0PzG3Yt.mjs +31 -0
- package/dist/document-B1w9aIq7.mjs +19 -0
- package/dist/{download-C4v7u-AV.mjs → download-B-l6ajeo.mjs} +2 -3
- package/dist/{eid-CP35PdEF.mjs → eid-BGl_X67N.mjs} +2 -3
- package/dist/{events-DmbzKIh_.mjs → events-BRRnroJb.mjs} +4 -5
- package/dist/{export-i8D6laBG.mjs → export-DEPLFq_E.mjs} +12 -25
- package/dist/{field-BKn9bt7I.mjs → field-BXaUzJvx.mjs} +5 -5
- package/dist/{fields-8_p81BqN.mjs → fields-LFMSMV5Z.mjs} +4 -5
- package/dist/{get-BvW21ujw.mjs → get-7-OiP8ma.mjs} +3 -4
- package/dist/{get-vvs0qzzM.mjs → get-B6lWlGeD.mjs} +3 -4
- package/dist/get-BM7MA5-7.mjs +31 -0
- package/dist/{get-CNEQK0-9.mjs → get-BUiHdwdU.mjs} +4 -5
- package/dist/{get-BHGgXqUq.mjs → get-BempHTsm.mjs} +3 -4
- package/dist/{get-CAzSP-7o.mjs → get-Bjvu5GM7.mjs} +3 -4
- package/dist/{get-vrccOMBk.mjs → get-BvI5QqjY.mjs} +3 -4
- package/dist/{get-BBOfshz1.mjs → get-C2DWxXYb.mjs} +3 -4
- package/dist/get-CapP9GiI.mjs +32 -0
- package/dist/{get-N4dOaTsp.mjs → get-Cjx_5Zm-.mjs} +4 -5
- package/dist/{get-DFkbi5L3.mjs → get-DAsSPKxF.mjs} +2 -3
- package/dist/{get-DnCE7K0y.mjs → get-DCHhq5Qv.mjs} +4 -5
- package/dist/get-DCf0O3GJ.mjs +31 -0
- package/dist/{get-CPTbySa7.mjs → get-DhxE02Ia.mjs} +3 -4
- package/dist/get-DiRtfImy.mjs +36 -0
- package/dist/{get-Bds6Izxk.mjs → get-DlQHyv4D.mjs} +3 -4
- package/dist/{get-DvLuY5Xu.mjs → get-Du11BNRd.mjs} +9 -17
- package/dist/{get-B0g6VMBC.mjs → get-DvCgLmPW.mjs} +3 -4
- package/dist/{get-BbHPFNqr.mjs → get-M3zgKoZr.mjs} +3 -4
- package/dist/{get-CWf46UFu.mjs → get-XcSH5Zyz.mjs} +5 -6
- package/dist/{get-run-4Ng8_awa.mjs → get-run-UJmyRo7H.mjs} +5 -6
- package/dist/{git-sync-DLzJrdMi.mjs → git-sync-BDnxPrqU.mjs} +3 -22
- package/dist/git-sync-DM5Nuv3a.mjs +28 -0
- package/dist/{git-sync-LQIVMNxO.mjs → git-sync-DxD_ng48.mjs} +2 -31
- package/dist/{group-YN6jj2AN.mjs → group-DBM87IgC.mjs} +2 -3
- package/dist/{has-remote-changes-BXPGI9xG.mjs → has-remote-changes-DmhFpgJe.mjs} +6 -14
- package/dist/{import-BoxOkA-G.mjs → import-BRC0TzWQ.mjs} +11 -28
- package/dist/{is-dirty-Cwxq4_gd.mjs → is-dirty-CT9e5vSd.mjs} +6 -15
- package/dist/{items-Cv6iAzrI.mjs → items-BSAEWZlw.mjs} +9 -17
- package/dist/{library-Cfw6eF35.mjs → library-ClLgrAXy.mjs} +1 -1
- package/dist/{library-fhVn1KDC.mjs → library-DfYAT2Ep.mjs} +1 -1
- package/dist/{library-CRaOhJnj.mjs → library-Zxh_MRVL.mjs} +5 -5
- package/dist/list-08dyODsP.mjs +36 -0
- package/dist/{list-C8J3n8B8.mjs → list-BOUmEn2X.mjs} +3 -4
- package/dist/{list--2T-GUEF.mjs → list-BZbC260R.mjs} +4 -5
- package/dist/{list-9uvk9dIA.mjs → list-BxCk3QTy.mjs} +3 -4
- package/dist/{list-BLJkJp5a.mjs → list-C3IRtLP6.mjs} +3 -4
- package/dist/list-CAalCea_.mjs +28 -0
- package/dist/{list-DhrapAcy.mjs → list-CleMHJBY.mjs} +3 -4
- package/dist/list-D6qH2PUE.mjs +38 -0
- package/dist/{list-oAAgU5uE.mjs → list-D9RmPIEJ.mjs} +9 -16
- package/dist/{list-DOaWCx8S.mjs → list-DCA0T8bS.mjs} +8 -17
- package/dist/{list-L2U1l6R4.mjs → list-DCBLu0KG.mjs} +3 -4
- package/dist/list-DDFk1IFP.mjs +35 -0
- package/dist/{list-WSJLnunb.mjs → list-DGLK2lso.mjs} +3 -4
- package/dist/{list-BwkfWtP2.mjs → list-DYlphSxV.mjs} +3 -4
- package/dist/{list-DOZNpX4b.mjs → list-De0ebgzc.mjs} +4 -5
- package/dist/{list-tutXEQRW.mjs → list-Dqyd2TZI.mjs} +4 -5
- package/dist/list-Drm6l_tt.mjs +28 -0
- package/dist/{list-BBp9XjYs.mjs → list-QFieRzSf.mjs} +3 -4
- package/dist/{list-CtZAu4im.mjs → list-kIMBSzS-.mjs} +4 -5
- package/dist/{login-DqWRF3Og.mjs → login-DDHEyjWz.mjs} +4 -5
- package/dist/{logout-ChCz1bkf.mjs → logout-BbB0vvQN.mjs} +2 -3
- package/dist/{measure-C5dIdFj1.mjs → measure-CGuwA0KX.mjs} +6 -6
- package/dist/{notification-DVrLkjFT.mjs → notification-Fcq1fIPw.mjs} +1 -1
- package/dist/{parameter-values-Dy4cBs5y.mjs → parameter-values-CI6-CkB7.mjs} +3 -4
- package/dist/{parse-id-DDCn3VXx.mjs → parse-id-CdZ0-1TM.mjs} +1 -1
- package/dist/{parse-ref-Wv21Roup.mjs → parse-ref-ibKVB0J4.mjs} +2 -6
- package/dist/{path-IYhuzjQV.mjs → path-Df2itv7t.mjs} +4 -5
- package/dist/{preflight-HLUVr2Yf.mjs → preflight-CoF1Em99.mjs} +1 -1
- package/dist/{publish-CMkCSt8i.mjs → publish-xUBySB1T.mjs} +4 -5
- package/dist/{pulse-a-83M4DV.mjs → pulse-DhG6I9mI.mjs} +1 -1
- package/dist/{query-tWQAV9pb.mjs → query-BkZvffIM.mjs} +6 -7
- package/dist/{query-Bka77J62.mjs → query-CmkC8sfu.mjs} +6 -7
- package/dist/{query-result-E4Q-Gra6.mjs → query-result-DBDwk8TS.mjs} +1 -1
- package/dist/{remove-collection-BV9wRQZD.mjs → remove-collection-DFfmuTwD.mjs} +5 -6
- package/dist/{render-DHjCcUXn.mjs → render-CI93iP0I.mjs} +1 -7
- package/dist/{replace-mt47dlRV.mjs → replace-DjoDBCLa.mjs} +3 -4
- package/dist/{rescan-values-6cRjcpTs.mjs → rescan-values-Ca2Wa0A2.mjs} +3 -4
- package/dist/{run-DI_pDxji.mjs → run-BCYATTjE.mjs} +5 -6
- package/dist/run-B_-pMw67.mjs +50 -0
- package/dist/{run-BmGmC-GQ.mjs → run-CP-chc5O.mjs} +3 -4
- package/dist/{runs-9Fu0mug5.mjs → runs-B0ypYKmn.mjs} +6 -7
- package/dist/{runtime-CNAZ_FuR.mjs → runtime-CJUDVwBu.mjs} +8 -103
- package/dist/{schema-tables-CLG0mtQb.mjs → schema-tables-Nl83e2Zm.mjs} +4 -5
- package/dist/{schemas-Cm5NWy59.mjs → schemas-Cp29qgn0.mjs} +4 -5
- package/dist/{search-CljYyf2F.mjs → search-BbktlnDC.mjs} +8 -14
- package/dist/{segment-IRzlQggt.mjs → segment-BHBoMpL9.mjs} +6 -6
- package/dist/{selectors-D4iOP5uB.mjs → selectors-e_0QdkaG.mjs} +2 -2
- package/dist/{send-D7DBnQbu.mjs → send-DEGpBT_7.mjs} +3 -4
- package/dist/{set-DKN8fuC6.mjs → set-C3tLEVuY.mjs} +2 -3
- package/dist/{set-active-CkE0Eiqh.mjs → set-active-CqvLJysE.mjs} +2 -3
- package/dist/{setting-CN4l8N-b.mjs → setting-Dwi-Wifx.mjs} +4 -4
- package/dist/{setup-BpTptmdO.mjs → setup-CcSVPnEI.mjs} +2 -3
- package/dist/{skills-BSAiLI3Z.mjs → skills-Cxi3fKMZ.mjs} +3 -3
- package/dist/{skills-hVKSOCUf.mjs → skills-DTCrMcix.mjs} +1 -1
- package/dist/{snippet-BxgvQ09J.mjs → snippet-CyKAgIZl.mjs} +2 -5
- package/dist/snippet-hrEAI5Yf.mjs +19 -0
- package/dist/{snippet-DecHB4Dn.mjs → snippet-oJLaYW9V.mjs} +1 -1
- package/dist/{stash-DJs6A6BA.mjs → stash-CVhkyRIT.mjs} +6 -7
- package/dist/{status-N_2EaWb7.mjs → status-B7tYgPVV.mjs} +6 -15
- package/dist/{status-DtwBDRtc.mjs → status-C9uhZuip.mjs} +16 -44
- package/dist/{subscription-BnGDp1oy.mjs → subscription-Cj9jyFEB.mjs} +6 -6
- package/dist/{subscriptions-BfFwV__x.mjs → subscriptions-B5oVkkZk.mjs} +6 -7
- package/dist/{summary-CHiq-4mz.mjs → summary-PVERfPr2.mjs} +3 -4
- package/dist/{sync-schema-DhnM-rpm.mjs → sync-schema-CtG6r6Pr.mjs} +4 -5
- package/dist/{sync-task-G1K47Nvn.mjs → sync-task-BACcrs0i.mjs} +1 -1
- package/dist/{table-BQaiCSNS.mjs → table-6IHwQyr6.mjs} +5 -5
- package/dist/timeline-BwQIj-le.mjs +21 -0
- package/dist/{timeline-event-B7H1y8s8.mjs → timeline-event-ox_Bw9F4.mjs} +6 -6
- package/dist/{transform-BvOEn41C.mjs → transform-D3QX0jxA.mjs} +1 -4
- package/dist/{transform-CiDOJGBW.mjs → transform-DoTyyXAu.mjs} +2 -2
- package/dist/transform-IPKGUzzd.mjs +28 -0
- package/dist/transform-job-COwH1ww7.mjs +22 -0
- package/dist/{transform-tag-jBVd6wXd.mjs → transform-tag-CcElGp3C.mjs} +4 -9
- package/dist/{transform-tag-DiXWRCQG.mjs → transform-tag-Dmb4u3-Q.mjs} +1 -1
- package/dist/{transform-tag-Dip51YBa.mjs → transform-tag-La1wmOTP.mjs} +5 -5
- package/dist/transform-test-C4VRGmGm.mjs +149 -0
- package/dist/transform-test-DGYx6MCT.mjs +16 -0
- package/dist/transform-test-w5hDTx_D.mjs +25 -0
- package/dist/{transforms-C-jYgS6z.mjs → transforms-DIgACzP-.mjs} +6 -7
- package/dist/tree-CQ0FXBnn.mjs +27 -0
- package/dist/{unpublish-BokbnLng.mjs → unpublish-Binr4uOG.mjs} +3 -4
- package/dist/{update-B9DcRSBQ.mjs → update-B4lIwY0G.mjs} +10 -16
- package/dist/update-B5NYEIHU.mjs +41 -0
- package/dist/{update-CPEU8ZEu.mjs → update-BKvSfdXu.mjs} +4 -5
- package/dist/{update-D9OTkZhw.mjs → update-BZS5m-vx.mjs} +3 -4
- package/dist/{update-C4PbAYgW.mjs → update-BbOX6axn.mjs} +3 -4
- package/dist/{update-e7lyQPxq.mjs → update-BiG9eLNH.mjs} +7 -13
- package/dist/{update-CpLEEbiJ.mjs → update-CIuoW1M9.mjs} +6 -7
- package/dist/{update-BBQInyiJ.mjs → update-CWIhy9Ax.mjs} +4 -5
- package/dist/{update-Bc8s1TL1.mjs → update-Cb68f4dx.mjs} +3 -4
- package/dist/{update-BErxnZrj.mjs → update-DH9CL61y.mjs} +4 -5
- package/dist/{update-AZSXMJqu.mjs → update-Db3j44fi.mjs} +7 -13
- package/dist/{update-BCGE5UYC.mjs → update-TDaf7Xbt.mjs} +4 -5
- package/dist/{update-dashcard-St-dGcE3.mjs → update-dashcard-DbZ8NlLZ.mjs} +3 -4
- package/dist/{update-BRXKLeJH.mjs → update-gGZjfZN3.mjs} +3 -4
- package/dist/{update-CfQoYVTc.mjs → update-la0K6wAf.mjs} +3 -4
- package/dist/{update-CKL2FtoX.mjs → update-lpLLpKhX.mjs} +4 -5
- package/dist/{update-rkbpH0lF.mjs → update-mOqedkLk.mjs} +3 -4
- package/dist/{upgrade-CUoRHfVg.mjs → upgrade-BCWXgRmG.mjs} +2 -3
- package/dist/upload-BVBs2F69.mjs +13 -0
- package/dist/{upload-BPd1Uzxc.mjs → upload-DUViVk1x.mjs} +2 -3
- package/dist/{uuid-CFN8LIkP.mjs → uuid-D3CGL8wF.mjs} +2 -3
- package/dist/{validate-query-Chc43ezW.mjs → validate-query-DaGYSmpQ.mjs} +1 -1
- package/dist/{values-BL_U-Zdw.mjs → values-8XUr04w3.mjs} +3 -4
- package/dist/{verify-DnBaRmkI.mjs → verify-deF_hThH.mjs} +2 -2
- package/dist/{wait-mPoOZxrw.mjs → wait-Ct0weHol.mjs} +7 -12
- package/dist/{wait-flags-DzytMEG8.mjs → wait-flags-Cb-bI0RD.mjs} +2 -2
- package/dist/{window-DSb1tqSe.mjs → window-BkvrP0gB.mjs} +1 -1
- package/package.json +1 -1
- package/skill-data/core/SKILL.md +19 -20
- package/skill-data/git-sync/SKILL.md +1 -91
- package/skill-data/transform/SKILL.md +60 -10
- package/skill-data/transform-test-plan/SKILL.md +170 -0
- package/skill-data/transform-test-plan/references/checklist.md +81 -0
- package/skill-data/transform-test-plan/references/checks.md +259 -0
- package/dist/archive-CqOKKrb2.mjs +0 -38
- package/dist/archive-DCjdICg7.mjs +0 -38
- package/dist/branch-BSeGrtP7.mjs +0 -28
- package/dist/cancel-task-BMQXtjW5.mjs +0 -34
- package/dist/card-RlzLDB2z.mjs +0 -24
- package/dist/collection-Bhe20ckt.mjs +0 -20
- package/dist/create-DwrhcI8r2.mjs +0 -144
- package/dist/create-branch-B3BMpF1g.mjs +0 -49
- package/dist/delete-D_OgiI4v.mjs +0 -86
- package/dist/dependencies-DEx0BsZ3.mjs +0 -41
- package/dist/dirty-dC1-Ud0G.mjs +0 -40
- package/dist/document-DOJbBWLF.mjs +0 -19
- package/dist/export-preflight-B5MvYhm3.mjs +0 -67
- package/dist/get-Cfagmy1i.mjs +0 -42
- package/dist/get-DwdMvWXU.mjs +0 -44
- package/dist/get-jV8G90Zy.mjs +0 -42
- package/dist/get-mGEv-kag.mjs +0 -33
- package/dist/git-sync-DjoaNmRM.mjs +0 -29
- package/dist/list-5Iojbr_p.mjs +0 -37
- package/dist/list-BD4onDqO.mjs +0 -37
- package/dist/list-C2yL7-CH.mjs +0 -45
- package/dist/list-CbbKip6U.mjs +0 -32
- package/dist/list-Dg2XeDON.mjs +0 -47
- package/dist/pin-BuZ_kg-u.mjs +0 -62
- package/dist/scope-DsGuWG5w.mjs +0 -11
- package/dist/snippet-LlHz3FdJ.mjs +0 -19
- package/dist/timeline-C2VW0Ph6.mjs +0 -21
- package/dist/transform-DAf0Y02t.mjs +0 -28
- package/dist/transform-job-ntiT5Rrh.mjs +0 -22
- package/dist/tree-DtyJhD54.mjs +0 -36
- package/dist/unpin-DxbXmLtW.mjs +0 -45
- package/dist/upload-Dc3QLhZw.mjs +0 -13
- package/dist/worktree-ChdDlG3Q.mjs +0 -25
- package/dist/worktree-DC0kTxxQ.mjs +0 -20
- package/dist/worktree-xAro1xAB.mjs +0 -24
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as createClient } from "./client-
|
|
1
|
+
import { n as createClient } from "./client-C9HBWl6A.mjs";
|
|
2
2
|
import { f as errorMessage, l as TimeoutError, o as MetabaseError, s as NetworkError } from "./predicates-Bkm2IoeX.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { F as tryParseTag, I as SessionProperties, u as USER_AGENT } from "./runtime-CJUDVwBu.mjs";
|
|
4
4
|
import { r as HttpError } from "./network-error-D6CiOEBw.mjs";
|
|
5
5
|
//#region ../client/src/version/probe.ts
|
|
6
6
|
const PROBE_PATH = "/api/session/properties";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { F as outputFlags, I as profileFlag, M as connectionFlags, c as renderSummary } from "./cli.mjs";
|
|
2
|
+
import { t as defineMetabaseCommand } from "./runtime-CJUDVwBu.mjs";
|
|
3
3
|
import { n as DEFAULT_TIMEOUT_MS, t as DEFAULT_INTERVAL_MS } from "./poll-75SvT7Zv.mjs";
|
|
4
|
-
import { r as parseWaitSchedule } from "./wait-flags-
|
|
5
|
-
import {
|
|
6
|
-
import { a as throwIfFailedTask, i as taskPollOptions, n as formatSyncTask, r as syncTaskIdleView, t as SyncTaskOrIdle } from "./sync-task-
|
|
4
|
+
import { r as parseWaitSchedule } from "./wait-flags-Cb-bI0RD.mjs";
|
|
5
|
+
import { c as syncTaskView } from "./git-sync-DxD_ng48.mjs";
|
|
6
|
+
import { a as throwIfFailedTask, i as taskPollOptions, n as formatSyncTask, r as syncTaskIdleView, t as SyncTaskOrIdle } from "./sync-task-BACcrs0i.mjs";
|
|
7
7
|
//#region src/commands/git-sync/wait.ts
|
|
8
8
|
const WaitResult = SyncTaskOrIdle;
|
|
9
9
|
var wait_default = defineMetabaseCommand({
|
|
@@ -15,13 +15,10 @@ var wait_default = defineMetabaseCommand({
|
|
|
15
15
|
minVersion: 60,
|
|
16
16
|
tokenFeature: "remote_sync"
|
|
17
17
|
},
|
|
18
|
-
worktree: "scoped",
|
|
19
|
-
details: WORKTREE_SCOPE_DETAIL,
|
|
20
18
|
args: {
|
|
21
19
|
...outputFlags,
|
|
22
20
|
...profileFlag,
|
|
23
21
|
...connectionFlags,
|
|
24
|
-
...worktreeFlag,
|
|
25
22
|
timeout: {
|
|
26
23
|
type: "string",
|
|
27
24
|
description: "Polling timeout in ms",
|
|
@@ -35,11 +32,9 @@ var wait_default = defineMetabaseCommand({
|
|
|
35
32
|
},
|
|
36
33
|
outputSchema: WaitResult,
|
|
37
34
|
examples: ["mb git-sync wait", "mb git-sync wait --timeout 300000 --json"],
|
|
38
|
-
async run({ args, ctx, getClient
|
|
35
|
+
async run({ args, ctx, getClient }) {
|
|
39
36
|
const schedule = parseWaitSchedule(args);
|
|
40
|
-
const
|
|
41
|
-
const scope = await getWorktree();
|
|
42
|
-
const final = await mb.gitSync.waitForTask(taskPollOptions(schedule), scopeQuery(scope));
|
|
37
|
+
const final = await (await getClient()).gitSync.waitForTask(taskPollOptions(schedule));
|
|
43
38
|
if (final === null) {
|
|
44
39
|
renderSummary({ status: "idle" }, syncTaskIdleView, "No git-sync task is running.", ctx);
|
|
45
40
|
return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as parseId } from "./parse-id-
|
|
1
|
+
import { t as interruptSignal } from "./cli.mjs";
|
|
2
|
+
import { t as parseId } from "./parse-id-CdZ0-1TM.mjs";
|
|
3
3
|
import { n as DEFAULT_TIMEOUT_MS, t as DEFAULT_INTERVAL_MS } from "./poll-75SvT7Zv.mjs";
|
|
4
4
|
//#region src/commands/wait-flags.ts
|
|
5
5
|
const waitScheduleFlags = {
|
package/package.json
CHANGED
package/skill-data/core/SKILL.md
CHANGED
|
@@ -6,17 +6,17 @@ allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
|
6
6
|
|
|
7
7
|
# metabase-cli (core)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
`mb` drives a Metabase instance over its REST API: CRUD on every resource, query and transform execution, search, git-sync (representations ↔ instance), and entity-id translation.
|
|
10
10
|
|
|
11
11
|
Top-level command groups (run `mb <group> --help` to discover verbs):
|
|
12
12
|
|
|
13
13
|
```
|
|
14
14
|
auth | db | table | field | upload | content-translation | query | card | dashboard | snippet | segment | measure | collection | library
|
|
15
|
-
document | timeline | timeline-event | transform | transform-job | transform-tag | alert | subscription | setting
|
|
16
|
-
search | git-sync |
|
|
15
|
+
document | timeline | timeline-event | transform | transform-job | transform-tag | transform-test | alert | subscription | setting
|
|
16
|
+
search | git-sync | setup | eid | uuid | upgrade | skills
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
The conventions below — auth, flags, output, body input — hold across **every** group.
|
|
19
|
+
The conventions below — auth, flags, output, body input — hold across **every** group. When a card needs a query, prefer MBQL over native SQL (portable, pre-flight-validated — load `mbql`); fall back to native SQL when MBQL can't express it.
|
|
20
20
|
|
|
21
21
|
## Auth & profiles
|
|
22
22
|
|
|
@@ -28,7 +28,7 @@ mb auth status --json # → {profile, present, url} for the d
|
|
|
28
28
|
mb auth status --profile <name> --json # health probe for one profile
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
`auth list` is the primary enumeration path — one call returns every profile with sanitized URL, an `authenticated` flag, and a probe `status` (`ok` / `auth-failed` / `network-error` / `server-error` / `not-probed`).
|
|
31
|
+
`auth list` is the primary enumeration path — one call returns every profile with sanitized URL, an `authenticated` flag, and a probe `status` (`ok` / `auth-failed` / `network-error` / `server-error` / `not-probed`).
|
|
32
32
|
|
|
33
33
|
- One profile and intent doesn't disambiguate → use it.
|
|
34
34
|
- Several → ask via `AskUserQuestion`, presenting the names from `auth list`.
|
|
@@ -47,8 +47,6 @@ Once a name is established, pass `--profile <name>` to **every** subsequent comm
|
|
|
47
47
|
|
|
48
48
|
**`--wait` for async operations.** `transform run`, `git-sync import`, and similar verbs return immediately by default. Pass `--wait` whenever the next step depends on completion — without it you race the operation and see "not ready" / transient connection refusals.
|
|
49
49
|
|
|
50
|
-
**`--worktree <id|branch>` scopes a command to a git-sync worktree** — an isolated checkout of one branch's content in the same instance (admin-only, v64+). Precedence: flag, then `MB_WORKTREE`, then the profile's pin (`mb worktree pin <ref>` / `unpin`). **The pin is a lock, not a default:** flag and env may only re-state it, a different worktree exits 2 — that is how a harness confines an agent to one worktree. Each command's `worktree` policy shows in `--help --json`: `scoped` honors the scope, `any` ignores it and takes no `--worktree`, `main-only` (`transform run`, main-app writes) refuses under one — report that refusal rather than unpinning. Workflow: the `git-sync` skill.
|
|
51
|
-
|
|
52
50
|
**Some "lookup" verbs return JSON envelopes, not bare values.** `mb setting get <key>` returns `{"key": "...", "value": ...}`. Extract before reusing:
|
|
53
51
|
|
|
54
52
|
```bash
|
|
@@ -62,7 +60,7 @@ Every list/get verb supports the same output flags:
|
|
|
62
60
|
- `--json` — emit the full JSON envelope, safe for `jq`. Default is human-readable text.
|
|
63
61
|
- `--full` — include every field (the compact projection is the default, and is the agent-facing contract).
|
|
64
62
|
- `--fields a,b.c.d` — project specific dot-paths. Mutually exclusive with `--full`. **Paths are relative to each `data[]` item on list verbs, and to the root on single-item verbs.** So it's `--fields id,name` on `… list` / `database schema-tables` (`data.id` and `data[].id` both fail with `unknown field path: "data.id"`), and `--fields id,name,display` on `card get`, `--fields data.rows` on `mb query` (whose `data` is an object).
|
|
65
|
-
- `--max-bytes <n>` — cap output size. Default 24576
|
|
63
|
+
- `--max-bytes <n>` — cap output size. Default 24576; `0` disables. On a list it drops trailing items and sets `truncated` (see below). Single-item commands (`get`) never truncate — over the cap they throw a `ConfigError` (exit 2: "output is N bytes, over the M-byte --max-bytes cap; …") whose tail names the remedy: on schema-shaped commands it is the exact narrower command to run instead — follow it rather than raising the cap.
|
|
66
64
|
- JSON output is a single line when stdout is piped (pretty-printed only at a TTY) — always parse it, never scrape by line position.
|
|
67
65
|
|
|
68
66
|
## List windows and resumption
|
|
@@ -74,7 +72,7 @@ Every list verb takes `--limit <n>` (items this call returns) and `--offset <n>`
|
|
|
74
72
|
- **`truncated` means the byte cap cut the output, not that the data ran out.** `has_more`/`next_offset` are recomputed to the cut point, so a capped list resumes like any window. Its `bytes` is what the untruncated answer would have measured, so it sizes the work left rather than the reply you hold. Narrow rows with `--fields` rather than raising `--max-bytes` — the cap counts only what you asked for, so `--fields` buys rows directly. A capped list always returns at least one row; when not even one fits it exits 2 with "the smallest response this list can produce is N bytes, over the M-byte --max-bytes cap; …".
|
|
75
73
|
- `limit` is echoed only when you passed `--limit` — except `mb search`, which defaults to `--limit 20` (an unbounded search is expensive server-side) and so always reports one. On nouns the server doesn't page, one large `--limit` with narrow `--fields` is a single request; many small `--offset` hops are one request each.
|
|
76
74
|
|
|
77
|
-
The whole walk
|
|
75
|
+
The whole walk:
|
|
78
76
|
|
|
79
77
|
```bash
|
|
80
78
|
offset=0
|
|
@@ -125,27 +123,27 @@ mb transform --help --json | jq -r '.commands[].command' # verbs under "transfo
|
|
|
125
123
|
|
|
126
124
|
## Resource quirks worth memorizing
|
|
127
125
|
|
|
128
|
-
|
|
126
|
+
Only what `--help` does _not_ tell you: footguns and non-obvious behaviors.
|
|
129
127
|
|
|
130
|
-
- **db traversal: the hydration ladder.** Start with `database get <db-id> --include tables` — the compact table map (id, name, schema, description per table), one call that fits most databases.
|
|
131
|
-
- **table fields.** `table get` never returns fields on its own — pass `--include fields` (compact;
|
|
128
|
+
- **db traversal: the hydration ladder.** Start with `database get <db-id> --include tables` — the compact table map (id, name, schema, description per table), one call that fits most databases. Pick the relevant tables, then `table fields <table-id>` per table (bounded: fields are per-table). `--include tables.fields` is the full rollup — small databases only. Hundreds of tables? Traverse by schema (`database schemas <db-id>` → `database schema-tables <db-id> <schema>`) or look tables up by name (`search <term> --models table --db-id <db-id> --limit 10`). `sync-schema` / `rescan-values` queue async work and return `{status:"ok"}` immediately; `sync-schema --wait` blocks until `initial_sync_status: complete`.
|
|
129
|
+
- **table fields.** `table get` never returns fields on its own — pass `--include fields` (compact; the underlying query_metadata response also carries FK targets and dimensions, visible under `--full`) or use `table fields <id>` (list envelope). `table update` patches table-level metadata only; physical columns aren't editable.
|
|
132
130
|
- **field has no `list`.** Fields are per-table — get them via `table get <id> --include fields`. Never enumerate fields across a whole db (context blow-up). `field summary` is live cardinality `{field_id, count, distincts}`; `field values` is the cached distinct set (`has_more_values: true` ⇒ truncated cache). `field update` patches metadata only (`base_type` isn't editable) — this is where you set a column's `semantic_type` or foreign-key target.
|
|
133
131
|
- **upload (CSV → tables).** `upload csv --file <path>` creates a new table + model (prints `{model_id, table_id}`); `upload append <table-id>` / `upload replace <table-id> --file <path>` add to / overwrite a table **previously created by upload** (columns must match). The destination db+schema is admin-configured, not per-call — check with `mb setting get uploads-settings --json` (`db_id: null` ⇒ uploads off/unconfigured; needs admin to read). `--collection <id|root>` only sets the model's collection. Max 50 MB. Errors: **"The uploads database is not configured."** = no db has uploads enabled; **"Uploads are not enabled."** = the append/replace target isn't an uploaded table.
|
|
134
|
-
- **content-translation.** EE-only (`content_translation` premium feature), admin-only, separate from Remote Sync. `content-translation download > translations.csv` streams the complete active dictionary; `content-translation upload --file translations.csv` replaces every active translation with the file. Always upload the canonical complete CSV, never a partial patch. An empty dictionary downloads as Metabase's four-row sample — don't re-upload it as real translations.
|
|
132
|
+
- **content-translation.** EE-only (`content_translation` premium feature), admin-only, and separate from Remote Sync. `content-translation download > translations.csv` streams the complete active dictionary; `content-translation upload --file translations.csv` replaces every active translation with the file's contents. Always upload the canonical complete CSV, never a partial patch. An empty dictionary downloads as Metabase's four-row sample dictionary — don't re-upload it as real translations. Metabase limits dictionaries to 1.5 MiB.
|
|
135
133
|
- **card.** `dataset_query` is the **flat** `mbql/query` value, not a legacy `{type:"query",query:…}` envelope (→ `mbql`). `--export-format csv|xlsx` streams the raw export (pipe to a file), bypassing the JSON envelope. `archive` is the only delete; unarchive with `update --body '{"archived":false}'`. `visualization_settings` keys are scoped by `display` and aren't pre-flighted — see `visualization`.
|
|
136
|
-
- **dashboard.** Dashcards round-trip through `PUT /api/dashboard/:id` (no per-dashcard endpoint): `update-dashcard <dash-id> <dashcard-id>` patches one safely; `update --body '{"dashcards":[…]}'` replaces the whole set (omitted ids are deleted server-side; negative ids for new cards). Every dashcard must include `card_id`, including existing rows; use `card_id:null` plus a `visualization_settings.virtual_card` block (`{display:"text"|"heading"|"link"|…}`) for non-question cards. `create` accepts the **same** `dashcards` array in its initial body, so lay out the whole dashboard in one call. `create`/`update` pre-flight every positive `card_id` and exit **2** with `{ok:false,errors:[…]}` on a bad ref (non-bypassable). `dashboard get <id>` (or `--full`) hydrates dashcards/tabs; `list` omits them. **The grid is 24 columns wide:** each dashcard's `{col, row, size_x, size_y}` is in grid units — **full-width is `size_x: 24`** (`size_x: 12` is half a row, the usual cause of a half
|
|
137
|
-
- **dashboard parameters (filters).** A dashboard's `parameters` array holds its filter widgets; they're part of the dashboard record, so read them with `dashboard get <id> --fields parameters --json` (no separate verb). **Editing replaces the _whole_ array** (like dashcards), so it's a read-modify-write loop and omitting a parameter deletes it. A parameter only filters a card once it is **mapped** onto that dashcard's `parameter_mappings` — an unmapped parameter is an inert widget. `type` is a **closed enum**; an unlisted value is a hard parse error that echoes the allowed set back. `dashboard parameter-values <id> <parameter-id> [--query <substr>]` fetches a widget's selectable values (`{values, has_more_values}`; `--query` is a case-insensitive substring
|
|
134
|
+
- **dashboard.** Dashcards round-trip through `PUT /api/dashboard/:id` (no per-dashcard endpoint): `update-dashcard <dash-id> <dashcard-id>` patches one safely; `update --body '{"dashcards":[…]}'` replaces the whole set (omitted ids are deleted server-side; negative ids for new cards). Every dashcard must include `card_id`, including existing rows; use `card_id:null` plus a `visualization_settings.virtual_card` block (`{display:"text"|"heading"|"link"|…}`) for non-question cards. `create` accepts the **same** `dashcards` array in its initial body, so lay out the whole dashboard in one call. `create`/`update` pre-flight every positive `card_id` and exit **2** with `{ok:false,errors:[…]}` on a bad ref (non-bypassable). `dashboard get <id>` (or `--full`) hydrates dashcards/tabs; `list` omits them. **The grid is 24 columns wide:** each dashcard's `{col, row, size_x, size_y}` is in grid units — **full-width is `size_x: 24`** (`size_x: 12` is half a row, the usual cause of a card filling only half the width). Keep `col + size_x ≤ 24`, start a full-width stack's `col` at 0, and don't overlap (the server stores collisions as sent — no auto-fix). Layout patterns and per-chart default sizes → the `dashboard` skill.
|
|
135
|
+
- **dashboard parameters (filters).** A dashboard's `parameters` array holds its filter widgets; they're part of the dashboard record, so read them with `dashboard get <id> --fields parameters --json` (no separate verb). **Editing replaces the _whole_ array** (like dashcards), so it's a read-modify-write loop and omitting a parameter deletes it. A parameter only filters a card once it is **mapped** onto that dashcard's `parameter_mappings` — an unmapped parameter is an inert widget. `type` is a **closed enum**; an unlisted value is a hard parse error that echoes the full allowed set back to you. `dashboard parameter-values <id> <parameter-id> [--query <substr>]` fetches a widget's selectable values (`{values, has_more_values}`; `--query` is a case-insensitive substring search). Parameter types, ids, mapping targets, and value sources → the `dashboard` skill; load it before authoring a `parameters` array.
|
|
138
136
|
- **alert / subscription are two unrelated systems.** `alert` watches one **card** and fires on a send condition (`/api/notification`: a cron string, `channel/email`-prefixed handlers, typed recipients); `subscription` delivers one **dashboard** on a schedule (`/api/pulse`: structured `schedule_type` + hour/day/frame, bare `email` channels, `{id}|{email}` recipients). The bodies are not interchangeable. Both silently deliver nowhere if the server has no SMTP / Slack app — check `mb setting get 'email-configured?'` (quote it; the `?` is a shell glob) before creating either. Their list-valued fields (`handlers`/`subscriptions`, `channels`/`cards`) **replace wholesale** on update, so adding one recipient is a read-modify-write. `mb card alerts <id>` and `mb dashboard subscriptions <id>` list what's already attached to a card/dashboard; `archive` deactivates rather than deletes. Load the `notification` skill before authoring either body.
|
|
139
137
|
- **snippet `--archived` is a swap, not a union** — list returns _either_ active _or_ archived rows, never both. (Same for `--filter archived` on dashboard/collection.)
|
|
140
138
|
- **segment / measure.** `update` and `archive` require a non-blank `revision_message` (audit-logged); the CLI does not synthesize it on `update`. `archive` defaults to `"Archived via mb CLI"` — override with `--revision-message`. `definition` is a flat MBQL clause (→ `mbql`): segment = a filter, measure = exactly one aggregation.
|
|
141
|
-
- **timeline / timeline-event.** Timelines are collection-scoped event annotations for time-series charts: a timeline's events render only on time-series questions saved in the **same collection** (`collection_id`; null = root) — sub-collections do **not** inherit, and events never draw on dashboard cards, only in the question (and collection) view.
|
|
139
|
+
- **timeline / timeline-event.** Timelines are collection-scoped event annotations for time-series charts: a timeline's events render only on time-series questions saved in the **same collection** (`collection_id`; null = root) — sub-collections do **not** inherit, and events never draw on dashboard cards, only in the question (and collection) view. To annotate a question's chart, create the timeline in that question's collection, then add events; an event only draws when its `timestamp` falls inside the chart's displayed time range. Event `create` requires `timestamp` (ISO 8601), `timezone` (IANA name), `time_matters` (true = the time of day is significant, false = date-only), and `timeline_id` — the API never auto-creates a default timeline (that's UI-only). There is no `timeline-event list`; enumerate with `timeline events <id>` (`--archived` to include archived). Archiving a timeline cascades `archived` to its events; `delete` is a **hard** delete of the timeline and all its events — prefer `archive`.
|
|
142
140
|
- **collection `<ref>`** accepts four forms only — positive int, `root`, `trash`, or a 21-char entity_id; anything else is a client-side `ConfigError`. `collection items` pages the server endpoint, pulling only as far as the output cap can show — read `has_more`/`next_offset` to continue. `collection tree` is **JSON-only** (`--format text` is rejected). A transform collection needs `collection create --namespace transforms`.
|
|
143
141
|
- **setting set** parses the value as **strict JSON**: a string is `'"value"'` (inner quotes), booleans `true`/`false`, numbers bare. Wrong quoting silently errors — confirm with `setting get <key>` after. `setting get --json` works on every value type (wrapping bare-text responses into `{key, value}`).
|
|
144
142
|
- **search vs. list.** For plain enumeration of cards/dashboards/collections use the dedicated `… list` verbs; reach for `search --models <kind>` only for ranking against a query string or a cross-resource lookup.
|
|
145
|
-
- **transform.** Iterate with `transform update <id>`, never `delete` + `create` (keeps the row, `entity_id`, materialized table, and YAML filename — avoids `_2` suffixes and noisy git history). `transform run` needs `--wait` (or `--sync`, which also waits for the output table to register and returns `target_table_id`) or you get only `{run_id, final:null}`. (→ `transform`.)
|
|
143
|
+
- **transform.** Iterate with `transform update <id>`, never `delete` + `create` (keeps the row, `entity_id`, materialized table, and YAML filename — avoids `_2` suffixes and noisy git history). `transform run` needs `--wait` (or `--sync`, which also waits for the output table to register and returns `target_table_id`) or you get only `{run_id, final:null}`. `transform-test run <id>` checks a transform against fixtures in temp tables — no real table is read — and exits non-zero unless it passes (v65+). (→ `transform`.)
|
|
146
144
|
- **setup is one-shot.** `mb setup` walks `/api/setup` for a **fresh** instance only — errors against an already-configured one. Mostly for bootstrapping local / e2e instances.
|
|
147
145
|
- **eid** translates a string entity id → numeric id: `mb eid --model <model> <eid1,eid2> --json`. Entity ids are NanoIDs that can start with `-`, which the positional form misreads as a flag (shell quotes don't help) — for those, use `--body '{"entity_ids":{"card":["-…"]}}'` (the id is a JSON string value, immune to flag parsing).
|
|
148
|
-
- **library.** EE-only (`library` premium feature, v59+). The Library is a curated subtree (`library-data` "Data" + `library-metrics` "Metrics" under a `library` root): tables published to **Data** appear first in data pickers and rank up in search; metrics saved to **Metrics** are prioritized in nav, search, and the query builder
|
|
146
|
+
- **library.** EE-only (`library` premium feature, v59+). The Library is a curated subtree (`library-data` "Data" + `library-metrics` "Metrics" under a `library` root): tables published to **Data** appear first in data pickers and rank up in search; metrics saved to **Metrics** are prioritized in nav, search, and the query builder. `library get` shows the Library and its Data/Metrics collection ids; `library create` provisions it (idempotent). `library publish --table-ids/--db-ids/--schemas` publishes tables into Data — it **resolves the Data collection itself and creates the Library if absent** (no collection id to find); each `--schemas` entry is `<db-id>:<schema>` (e.g. `1:public`), not a bare name. `publish` cascades to upstream FK dependencies, `unpublish` to downstream dependents; both need **admin or data-analyst** (Curate alone won't publish) and exit **403** without write **and** query permission on every affected table. Publish status shows on the table: `table get`/`table list` carry `is_published` (`collection_id` under `--full`). Publish finished, analysis-ready tables — clean/combine via transforms first. Publishing does not put the Data collection in the git-sync scope: on a remote-sync instance, `mb git-sync add-collection <data-collection-id>` is what makes exports carry the published tables' metadata (see the `git-sync` skill).
|
|
149
147
|
- **query / uuid.** `mb query` is the ad-hoc MBQL surface (`--print-schema` → `--dry-run` → run); `mb uuid --count <n>` mints the `lib/uuid` values MBQL clauses need. Both live in `mbql`.
|
|
150
148
|
|
|
151
149
|
## Specialized skills (load on demand)
|
|
@@ -159,9 +157,10 @@ This file is enough for any single-command task. For anything deeper, load the r
|
|
|
159
157
|
- **`metadata`** — setting field/table metadata: semantic types, foreign-key targets, dropdown/scan behavior, and column visibility, and the downstream features each unlocks. Load when editing what a column _means_, not its data.
|
|
160
158
|
- **`notification`** — scheduled delivery: question alerts (`mb alert`) and dashboard subscriptions (`mb subscription`). Choosing between them, the two schedule/recipient contracts, channel prerequisites, testing a send.
|
|
161
159
|
- **`transform`** — transform body JSON, create + run-with-wait, run inspection, tags, jobs.
|
|
160
|
+
- **`transform-test-plan`** — deciding _what_ to test in a transform: the fixture cast, the expectations, the coverage matrix. (`transform` has the `mb transform-test` shapes.)
|
|
162
161
|
- **`document`** — Metabase documents (TipTap body, embedding cards).
|
|
163
|
-
- **`git-sync`** — round-tripping content to/from a git remote
|
|
164
|
-
- **`data-workflow`** — the guided, end-to-end data workflow: investigate raw data, build clean analysis-ready tables, define reusable segments/measures/metrics, answer questions, build dashboards. **Start here when the user states a goal rather than a single verb** — "make sense of my data", "build a data model", "go from raw data to a dashboard", "set up analytics for X". It detects where the data is and routes to the right stage.
|
|
162
|
+
- **`git-sync`** — round-tripping content to/from a git remote.
|
|
163
|
+
- **`data-workflow`** — the guided, end-to-end data workflow: investigate raw data, build clean analysis-ready tables, define reusable segments/measures/metrics, answer questions, build dashboards. **Start here when the user states a goal rather than a single verb** — "make sense of my data", "build a data model", "go from raw data to a dashboard", "be my data analyst", "set up analytics for X". It detects where the data is and routes to the right stage.
|
|
165
164
|
|
|
166
165
|
If a task spans more than one, load each. `mb skills list` enumerates everything on the installed version.
|
|
167
166
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: git-sync
|
|
3
|
-
description: Round-trip Metabase content (cards, dashboards, transforms, snippets, collections, Library-published table/field metadata) between an instance and a git remote via `mb git-sync …` — status, dirty / has-remote-changes checks, import, export (with branch guard), branches, stash, add/remove a collection from sync. Load when the user wants to "import the latest changes", "export to git", "push my changes to the repo", "open a PR with my Metabase changes", "git sync", "dirty check", "stash before pulling", "add a collection to sync", or anything `mb git-sync
|
|
3
|
+
description: Round-trip Metabase content (cards, dashboards, transforms, snippets, collections, Library-published table/field metadata) between an instance and a git remote via `mb git-sync …` — status, dirty / has-remote-changes checks, import, export (with branch guard), branches, stash, add/remove a collection from sync. Load when the user wants to "import the latest changes", "export to git", "push my changes to the repo", "open a PR with my Metabase changes", "git sync", "dirty check", "stash before pulling", "add a collection to sync", or anything `mb git-sync …`.
|
|
4
4
|
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -40,7 +40,6 @@ Pulls the configured branch and applies it to the instance. Polls until the task
|
|
|
40
40
|
| `--branch <name>` | Defaults to the `remote-sync-branch` setting; override per-call. |
|
|
41
41
|
| `--no-wait` | Return as soon as the task is queued; combine with `mb git-sync wait` later. |
|
|
42
42
|
| `--force` | **Discards local Metabase-side dirty changes** (lossy). Confirm with the user first. |
|
|
43
|
-
| `--merge` | Three-way merge remote changes instead of failing on divergence. Confirm first. |
|
|
44
43
|
| `--timeout <ms>` | Polling deadline. Default 600 000. |
|
|
45
44
|
| `--interval <ms>` | Polling cadence. Default 2 000. |
|
|
46
45
|
|
|
@@ -63,7 +62,6 @@ Pushes Metabase-side changes back to the configured remote. `-m` is the commit m
|
|
|
63
62
|
| `--branch <name>` | Push to a specific branch instead of the configured one. |
|
|
64
63
|
| `-m, --message <s>` | Commit message. |
|
|
65
64
|
| `--force` | Force-push / overwrite remote. Confirm with the user. |
|
|
66
|
-
| `--merge` | Three-way merge instead of failing on divergence. |
|
|
67
65
|
| `--no-wait` | Don't poll. |
|
|
68
66
|
|
|
69
67
|
Workflow:
|
|
@@ -98,94 +96,6 @@ mb git-sync stash --profile <n> # export current state
|
|
|
98
96
|
|
|
99
97
|
`stash` is the safe move when the instance has team work you don't want to lose, but you need to pivot to a different branch (`import` would discard, `export --force` would overwrite). It exports current state to a fresh branch first.
|
|
100
98
|
|
|
101
|
-
## Worktrees (isolated branch checkouts)
|
|
102
|
-
|
|
103
|
-
A **worktree** is a self-contained checkout of one branch's content inside the same instance. Transforms, transform tags, snippets, `transforms`-namespace collections, the Library, cards, dashboards, and documents are tagged with the worktree's id; tables and fields stay shared with the main app. It is how a chain of transforms gets built, exported, and reviewed as a PR without touching production content. Admin-only, and needs Metabase v64+ with the `remote_sync` feature.
|
|
104
|
-
|
|
105
|
-
Two hard limits, up front:
|
|
106
|
-
|
|
107
|
-
- **A worktree's transforms cannot be run.** `mb transform run` refuses while a worktree scope is in force, and the server rejects the run anyway. There is no workaround to find — run the transform in the main app after the branch is merged and imported, and say so rather than trying to validate the SQL by running it in the worktree.
|
|
108
|
-
- **A worktree is bound to its branch for life.** No branch switching inside one; a different branch means a different worktree.
|
|
109
|
-
|
|
110
|
-
### Where am I? Check before touching import/export
|
|
111
|
-
|
|
112
|
-
```bash
|
|
113
|
-
mb auth status --profile <n> --json | jq '.worktree' # {id, branch} when the profile is pinned, else null
|
|
114
|
-
mb git-sync status --profile <n> --json | jq '.worktree' # the scope this command ran under
|
|
115
|
-
mb worktree list --profile <n> --json # every worktree on the instance
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
A pinned profile pushes to the worktree's branch; an unpinned one pushes to whatever `remote-sync-branch` points at. Confusing the two is how worktree work lands on the team's tracked branch, so read the pin before the first `export`.
|
|
119
|
-
|
|
120
|
-
### Preconditions before creating one
|
|
121
|
-
|
|
122
|
-
1. **The user named the branch.** Create a worktree only for a branch the user asked you to work on.
|
|
123
|
-
2. **A branch holds at most one worktree.** Check `mb worktree list --json` first; a duplicate fails with the server's `A worktree for branch '<b>' already exists.` — reuse it with `mb worktree pin <branch>` instead.
|
|
124
|
-
3. **A pinned profile is already confined.** `mb worktree create` for any other branch is refused, because a second worktree from a pinned session would escape the pin.
|
|
125
|
-
|
|
126
|
-
### The workflow
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
# 1. Create the worktree and pin the profile to it. Mints the branch on the remote when missing
|
|
130
|
-
# (--no-checkout, so the main app's tracked branch is untouched), then pulls the branch in.
|
|
131
|
-
mb worktree create feat/order-metrics --pin --profile <n> --json
|
|
132
|
-
|
|
133
|
-
# 2. Edit transforms. Scoped commands need no extra flag once the profile is pinned.
|
|
134
|
-
mb transform create --file ./.scratch/transform.json --profile <n> --json
|
|
135
|
-
mb transform list --profile <n> --json # only this worktree's transforms
|
|
136
|
-
mb transform update <id> --file ./.scratch/patch.json --profile <n> --json
|
|
137
|
-
|
|
138
|
-
# 3. Read state before pushing — same rule as the main app.
|
|
139
|
-
mb git-sync status --profile <n> --json # branch, dirty flag, current task, all scoped
|
|
140
|
-
mb git-sync dirty --profile <n> --json # exactly what will be committed
|
|
141
|
-
|
|
142
|
-
# 4. Dry-run the push, read the answer, then push.
|
|
143
|
-
mb git-sync export-preflight --profile <n> --json
|
|
144
|
-
mb git-sync export -m "add order metrics transforms" --profile <n>
|
|
145
|
-
|
|
146
|
-
# 5. Open the PR from the branch with plain git / gh, and let a human review and merge it.
|
|
147
|
-
|
|
148
|
-
# 6. Retire the worktree once the PR is merged. This clears the pin it was holding.
|
|
149
|
-
mb worktree delete feat/order-metrics --profile <n> --json
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
`export-preflight` answers `{has_changes, clean, conflicts, summary: {added, updated, removed}, force_push_casualties: {deleted, overwritten}, reason}`. Read it before every worktree export: `clean: true` with empty `conflicts` is a push that applies as-is; a non-empty `conflicts` or `force_push_casualties` is a conversation with the user, not a `--force`.
|
|
153
|
-
|
|
154
|
-
The main app pulls the merged branch from a **different, unpinned** profile — `git-sync import` into the main app and `transform run` are both main-app operations and a pinned profile refuses them:
|
|
155
|
-
|
|
156
|
-
```bash
|
|
157
|
-
mb git-sync import --profile <main-app-profile>
|
|
158
|
-
mb transform run <id> --wait --profile <main-app-profile> --json
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### Scope precedence and what a pinned profile refuses
|
|
162
|
-
|
|
163
|
-
The scope comes from `--worktree <id|branch>`, else `MB_WORKTREE`, else the profile's pin. **The pin is a lock, not a default:** while it stands, the flag and the env var may only re-state it, and naming a different worktree exits 2 with `profile "<p>" is pinned to worktree <id> (<branch>); refusing --worktree <x>`.
|
|
164
|
-
|
|
165
|
-
Under a scope, a command that changes or runs main-app state refuses before any request and exits 2 — `transform run` / `cancel`, `card` / `library` writes, and the main-app git-sync verbs `stash`, `create-branch`, `add-collection`, `remove-collection`:
|
|
166
|
-
|
|
167
|
-
```
|
|
168
|
-
transform run is not available inside a worktree (scope: worktree 3 (feat/order-metrics) from the profile pin); it changes main-app content. Unpin the profile (`mb worktree unpin`) or drop MB_WORKTREE to run it against the main app.
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Two more refusals belong to the scope:
|
|
172
|
-
|
|
173
|
-
- Fetching a row that lives elsewhere: `transform 12 is not in worktree 3 (feat/order-metrics); refusing to touch main-app content`.
|
|
174
|
-
- `--branch` on `import` / `export` / `export-preflight`: `a worktree is pinned to its branch; drop --branch`.
|
|
175
|
-
|
|
176
|
-
### Confirmations these flags need
|
|
177
|
-
|
|
178
|
-
- `--force` (on `import`, `export`, and `worktree delete`) and `--merge` (on `import` / `export`) are **lossy or history-rewriting** — ask the user first, with `AskUserQuestion`, naming what gets discarded. `export-preflight`'s `force_push_casualties` is the list to show them.
|
|
179
|
-
- `mb worktree delete` refuses a worktree holding unpushed changes: `worktree <id> (<branch>) has unpushed changes; push them with mb git-sync export or pass --force to discard`. Export first, or ask before forcing — the content in a worktree exists nowhere else once it is gone.
|
|
180
|
-
|
|
181
|
-
### Don't (worktrees)
|
|
182
|
-
|
|
183
|
-
- **Don't unpin to reach the main app.** The pin is the isolation boundary a harness put there; `mb worktree unpin` to get past a refusal turns an isolated session into one that can write production content. Report the refusal and what you would need instead.
|
|
184
|
-
- **Don't run a worktree's transforms**, and don't route around the refusal with `mb query`, a native card, or a second profile. Validate the query shape with `--dry-run` (see `mbql`) and run it for real in the main app after the merge.
|
|
185
|
-
- **Don't create worktrees for branches you were not asked to touch.** A worktree checks a whole branch's content into the instance; an unwanted one is content to clean up, not a free experiment.
|
|
186
|
-
- **Don't `git-sync export` from the main app while the work lives in a worktree.** It pushes the main app's state to the tracked branch and the worktree's edits are not in it. Check `.worktree` in `git-sync status` before exporting.
|
|
187
|
-
- **Don't hand-write the branch's YAML in the repo to "help" the PR.** Worktree content round-trips through `export` exactly like main-app content — see the first entry of the general "Don't" list below.
|
|
188
|
-
|
|
189
99
|
## Polling and cancelling
|
|
190
100
|
|
|
191
101
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: transform
|
|
3
|
-
description: Author and run Metabase transforms via `mb` — body shape (native SQL or structured MBQL), create + run-with-wait, run inspection, dependencies, cancel, the `update`-vs-recreate iteration rule, the writable-keys-only PATCH contract, plus transform tags and tag-driven transform-job schedules. Load when the user touches transforms — "create a transform", "run a transform", "fix a failing transform", "list transform runs", "cancel a running transform", "manage transform tags", "run a transform job", or anything `mb transform …` / `mb transform-job …` / `mb transform-tag …`.
|
|
3
|
+
description: Author and run Metabase transforms via `mb` — body shape (native SQL or structured MBQL), create + run-with-wait, run inspection, dependencies, cancel, the `update`-vs-recreate iteration rule, the writable-keys-only PATCH contract, transform tests (fixtures and expectations run against temp tables), plus transform tags and tag-driven transform-job schedules. Load when the user touches transforms — "create a transform", "run a transform", "fix a failing transform", "test a transform", "list transform runs", "cancel a running transform", "manage transform tags", "run a transform job", or anything `mb transform …` / `mb transform-job …` / `mb transform-tag …` / `mb transform-test …`.
|
|
4
4
|
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -48,7 +48,7 @@ mb transform run "$TRANSFORM_ID" --wait --profile <name> --json
|
|
|
48
48
|
- `--wait` polls until status is `succeeded` or `failed`. Without it you get only `{message: "Transform run started", run_id, final: null}` and must poll yourself — don't put bare `transform run` in a tight loop; let `--wait` do the polling.
|
|
49
49
|
- `--sync` implies `--wait`, then waits until the run registers its output table (the run registers it itself — no `db sync-schema` needed), adding `target_table_id` to the envelope. Use it when you'll build MBQL on the output (see "Inspect").
|
|
50
50
|
- The `--json` envelope is shape-stable: `{message, run_id, final}` (plus `target_table_id` under `--sync` — a number, or `null` if the table didn't register before the timeout). `final` is `null` when `--wait` is omitted or the run never started, otherwise a full `TransformRun` with `status` and `message`. On a failed run (`final.status` ∈ {`failed`, `timeout`, `canceled`}) the CLI exits 1 and writes a one-line `transform run <id> failed` to stderr; the failure detail lives only in `final.message` on stdout, so `jq -r '.final.message'` is where to look.
|
|
51
|
-
- `transform create --json` returns the agent-facing compact projection: `{id, name, description, source_type, target: {type, database, schema, name}, target_db_id
|
|
51
|
+
- `transform create --json` returns the agent-facing compact projection: `{id, name, description, source_type, target: {type, database, schema, name}, target_db_id}`. Read `target.schema`/`target.name` directly off it — no follow-up `transform get`.
|
|
52
52
|
- If a transform with the same `name` already has a YAML representation on disk under the configured remote-sync repo, `create` mints a `_2` suffix on the exported filename (the new transform gets a fresh `entity_id`; the prior one isn't touched). For "iterate on the same concept", prefer `transform update <id>` — see "Iterating on a failing transform".
|
|
53
53
|
- **`collection_id` only accepts a collection in the `:transforms` namespace.** Transforms aren't filed next to cards and dashboards — a normal analytics collection id fails create/update with `collection_id: A Transform can only go in Collections in the :transforms namespace.` Omit `collection_id` to leave the transform uncollected (the common case), or provision one with `mb collection create --body '{"name":"…"}' --namespace transforms --json` (see `core`) and pass the returned `id`. Cards and dashboards you build **on top of** the output table go in ordinary collections — so "put the transform and its dashboard in collection X" means _X holds the dashboard + cards; the transform stays in the transforms namespace._
|
|
54
54
|
|
|
@@ -71,14 +71,6 @@ On `target_table_id: null` (still syncing when the poll timed out; exit 0) re-po
|
|
|
71
71
|
|
|
72
72
|
Columns and types are inferred from the result set; change the SELECT shape and the next run fails on a column mismatch — drop the table first (`transform delete-table <id>`). A changed shape also needs a re-run with `--sync` before MBQL sees the new/renamed columns.
|
|
73
73
|
|
|
74
|
-
## Editing transforms safely in a worktree
|
|
75
|
-
|
|
76
|
-
A **worktree** is an isolated checkout of one git branch's content inside the same instance — the way to build a chain of transforms for PR review without touching production transforms or their tables. The lifecycle (create → pin → export-preflight → export → PR → delete) lives in the `git-sync` skill, section "Worktrees": **`mb skills get git-sync`**. What matters while authoring:
|
|
77
|
-
|
|
78
|
-
- **`worktree_id` says where a transform lives.** `transform get` and `transform list` carry it: an integer for a worktree's transform, `null` for a main-app one. Read it before editing anything you did not create in this session — under a scope, `update` / `delete` / `delete-table` of a main-app transform is refused with `transform <id> is not in worktree <n> (<branch>); refusing to touch main-app content`.
|
|
79
|
-
- **The scope is implicit once the profile is pinned.** With `--worktree <id|branch>`, `MB_WORKTREE`, or a pin in force, `transform list` shows only that worktree's transforms and `transform create` files what it creates into it. Without a scope both address the main app, whose listing excludes every worktree's rows.
|
|
80
|
-
- **`transform run` is refused under a scope**, and the server rejects worktree runs anyway: `transform run is not available inside a worktree (…); it changes main-app content.` The create → run → fix loop therefore does not close inside a worktree. Validate the query with `--dry-run` (see `mbql`) before creating, iterate the body with `transform update`, and run it in the main app once the branch is merged and imported. Don't unpin, don't switch to another profile, and don't stand in a run with `mb query` — tell the user the run waits for the merge.
|
|
81
|
-
|
|
82
74
|
## Inspect runs and cancel an in-flight run
|
|
83
75
|
|
|
84
76
|
```bash
|
|
@@ -180,6 +172,64 @@ mb transform run "$ID" --wait --profile <n> --json # → succeeded
|
|
|
180
172
|
|
|
181
173
|
If you really must `create + delete` instead, do the `delete` **before** the first `git-sync export` so the failed entity never lands in git history — an export of a soft-failed state is noise that needs a follow-up cleanup commit. See `git-sync`, "Read state before mutating", for the ordering rule.
|
|
182
174
|
|
|
175
|
+
## Transform tests (v65+)
|
|
176
|
+
|
|
177
|
+
A transform test replaces every table the transform reads with a fixture, runs it into a temp table, and checks that output. No real table is read or written.
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
mb transform-test list --transform <id> --profile <n> --json # --transform is optional
|
|
181
|
+
mb transform-test get <id> --full --profile <n> --json # --full for inputs/expectations
|
|
182
|
+
mb transform-test create --file ./.scratch/test.json --profile <n> --json
|
|
183
|
+
mb transform-test update <id> --file ./.scratch/patch.json --profile <n> --json
|
|
184
|
+
mb transform-test delete <id> --yes --profile <n>
|
|
185
|
+
mb transform-test run <id> --profile <n> --json # exits non-zero unless it passes
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**`inputs`** — one per table the transform reads, each naming a `table` plus either `format: "sql"` with `sql`, or `format: "rows"` with `columns` (each a `name` and a `cast_type`) and `rows`.
|
|
189
|
+
|
|
190
|
+
**`cast_type` is a `CAST` target, not a column type**, and the two vocabularies differ per warehouse: MySQL casts to `SIGNED` and reports `INTEGER`; ClickHouse takes `Nullable(Int32)` for a column that is `Int64`. So a body is warehouse-specific — don't copy a `database_type` out of a run result into a `cast_type`, and don't expect one body to run everywhere.
|
|
191
|
+
|
|
192
|
+
**`expectations`** — `type: "empty"` with the `sql` that must return no rows, or `type: "equals"`, which needs the same `format` split as an input (`"rows"` with `columns`/`rows`, or `"sql"` with a query). An `equals` without a `format` is refused.
|
|
193
|
+
|
|
194
|
+
**An `empty` query may only name the transform's target table and its declared input tables.** Those are rewritten to the run's temp tables; any other table you name is left exactly as written and reads the real one — the single way a test run can touch production data.
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"transform_id": 1,
|
|
199
|
+
"name": "adults only",
|
|
200
|
+
"inputs": [
|
|
201
|
+
{
|
|
202
|
+
"table": { "schema": "public", "name": "people" },
|
|
203
|
+
"format": "rows",
|
|
204
|
+
"columns": [
|
|
205
|
+
{ "name": "id", "cast_type": "INTEGER" },
|
|
206
|
+
{ "name": "age", "cast_type": "INTEGER" }
|
|
207
|
+
],
|
|
208
|
+
"rows": [
|
|
209
|
+
{ "id": 1, "age": 30 },
|
|
210
|
+
{ "id": 2, "age": 12 }
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
],
|
|
214
|
+
"expectations": [
|
|
215
|
+
{
|
|
216
|
+
"type": "equals",
|
|
217
|
+
"name": "exactly one row, id 1",
|
|
218
|
+
"format": "rows",
|
|
219
|
+
"columns": [{ "name": "id", "cast_type": "INTEGER" }],
|
|
220
|
+
"rows": [{ "id": 1 }]
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"type": "empty",
|
|
224
|
+
"name": "no null ids",
|
|
225
|
+
"sql": "SELECT * FROM public.adults WHERE id IS NULL"
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Create and update bodies are closed — strip `id`, `entity_id`, `creator_id`, `created_at` and `updated_at` from a `get --full` body before sending it back.
|
|
232
|
+
|
|
183
233
|
## Drop the materialized table (keep the transform)
|
|
184
234
|
|
|
185
235
|
```bash
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: transform-test-plan
|
|
3
|
+
description: Derive a comprehensive test plan for a transform — the fixture cast, the expectations, hand-derived expected rows, and a coverage matrix — from the model's declared design. Covers input partitioning (zero-case, multiplicity, dirty rows), grain / conservation / recomputation / conformance checks, and known-quirk conventions. Load when the user wants tests planned or written for transforms — "write tests for my transforms", "is my model right", "test plan for this pipeline", "add data quality checks" — whether the model is mid-build or already deployed. The `mb transform-test` command and body shapes live in the `transform` skill; this one decides what to test.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Planning transform tests
|
|
8
|
+
|
|
9
|
+
Turn a transform into a fixture cast that proves the logic on small known rows, expectations that
|
|
10
|
+
state the model's invariants, and a coverage matrix showing what's checked and what's deliberately
|
|
11
|
+
not. Mechanics — the `inputs`/`expectations` body shape and every `mb transform-test` verb — live in
|
|
12
|
+
the `transform` skill (`mb skills get transform`); load it before authoring, and never restate it
|
|
13
|
+
here.
|
|
14
|
+
|
|
15
|
+
Every check derives from what the model **declares** — detected from its SQL, confirmed with its
|
|
16
|
+
owner — never from conformance to a modeling doctrine. One plan serves two moments: while the model
|
|
17
|
+
is **built**, checks pin each design decision; once **deployed**, the same SQL screens production
|
|
18
|
+
tables for anomalies.
|
|
19
|
+
|
|
20
|
+
## Operating rules
|
|
21
|
+
|
|
22
|
+
- **Detect, then derive.** Classify what the transform is and which conventions it uses (the
|
|
23
|
+
checklist); derive checks only from that. Star, one-big-table, partial denormalization — all
|
|
24
|
+
fine; never flag a style.
|
|
25
|
+
- **Judgment calls go through the checklist.** The session's autonomy setting governs which answers
|
|
26
|
+
you supply yourself and which you bring to the user — it never makes the checklist a formality.
|
|
27
|
+
Every answer you supply yourself is recorded in the plan as a stated assumption, paired with the
|
|
28
|
+
named expectation that enforces it — reversing the decision then breaks a test, not a paragraph.
|
|
29
|
+
And regardless of setting, when genuinely unsure, ask — a wrong-but-confident grain poisons every
|
|
30
|
+
downstream check.
|
|
31
|
+
- **Expected rows are derived by hand** from the fixture story and business meaning — never captured
|
|
32
|
+
from the transform's output, which asserts only that the transform equals itself.
|
|
33
|
+
- **Batteries stay off until declared structure switches them on.** No snapshot-density checks
|
|
34
|
+
without a snapshot, no version-history checks without effective/end/current columns. An empty
|
|
35
|
+
section beats a speculative one.
|
|
36
|
+
|
|
37
|
+
## The procedure
|
|
38
|
+
|
|
39
|
+
1. **Profile the real inputs**: per table, row count; per column, min/max/distinct-count/null
|
|
40
|
+
incidence; orphan counts across declared links (`mb field summary`, `mb query`). Profiling feeds
|
|
41
|
+
domains, bounds, null partitions, and key candidates — and every fixture edge cites the real-data
|
|
42
|
+
condition that warrants it, with its count ("the warehouse has 67 ship-before-order rows").
|
|
43
|
+
2. **Detect.** Read the transform's SQL (`mb transform get <id> --full --json`) for: grain
|
|
44
|
+
candidates (`GROUP BY` keys, the joins' driving table), join types (orphan handling), correlated
|
|
45
|
+
aggregates (stored aggregates), `<entity>_<attr>` naming (copies), effective/end/current columns
|
|
46
|
+
(version history), `now()`/`current_date` (volatile columns). A transform whose grain you cannot
|
|
47
|
+
state in one phrase is itself a finding — raise it before writing any test.
|
|
48
|
+
3. **Confirm.** Walk [references/checklist.md](references/checklist.md). Three stages: classify the
|
|
49
|
+
model, per-table declarations, per-column declarations. Each question carries its detection hint;
|
|
50
|
+
answer autonomously where the hint resolves, ask where it doesn't. In build-along mode these are
|
|
51
|
+
design questions — treat an undecided answer as a decision to make together, not a blocker.
|
|
52
|
+
4. **Derive.** Route every output column through [references/checks.md](references/checks.md) —
|
|
53
|
+
declared property → expectation shape, fixture implication, expected-row convention. Read it in
|
|
54
|
+
full once per plan; it is the plan's content.
|
|
55
|
+
5. **Design the fixture cast**: one small cast per transform (≈5–10 rows per table), human-named
|
|
56
|
+
rows ("Alice Premium"), every row a named edge — zero-case entities for every outer join and
|
|
57
|
+
aggregation, ≥2-member groups for every grouping and join, one dirty row per screenable defect,
|
|
58
|
+
boundary dates. Document it as a table (row → attributes → purpose) in the plan.
|
|
59
|
+
|
|
60
|
+
Express the cast as literal rows rather than as a query, so the story stays legible in the test
|
|
61
|
+
itself; a query is worth it only when the rows are mechanical to generate. A cast is written
|
|
62
|
+
against one warehouse and does not carry to another.
|
|
63
|
+
|
|
64
|
+
6. **Hand-derive the expected rows**, arithmetic recorded in the plan (premium: 3 orders / 350.50 /
|
|
65
|
+
3.0). Every fixture row's fate appears in some expected cell. Pin NULL-vs-0-vs-empty for every
|
|
66
|
+
zero-case row — that cell is the null policy's only enforcement.
|
|
67
|
+
7. **Author the test.** One test per transform, per coherent story: an `equals` pinning the output,
|
|
68
|
+
and `empty` expectations stating the invariants that survive a change to the cast.
|
|
69
|
+
|
|
70
|
+
Give each expectation a name that states the invariant, because the name is what a
|
|
71
|
+
failure leads with ("revenue never negative", not "check 3"), and open each `empty` expectation's
|
|
72
|
+
SQL with a `--` contract comment: the invariant, and the failure modes it catches ("catches both
|
|
73
|
+
dropped orders and join fan-out").
|
|
74
|
+
|
|
75
|
+
Where one invariant applies to several transforms, duplicate the SQL. Nothing is shared between
|
|
76
|
+
tests, so give each copy its own name and comment rather than one that only makes sense next to
|
|
77
|
+
its twin.
|
|
78
|
+
|
|
79
|
+
8. **Emit the coverage matrix** in the plan: rows = output columns with the table's grain; columns =
|
|
80
|
+
check classes (grain, conservation, recomputation, conformance, domains, referential integrity,
|
|
81
|
+
temporal, screens); cells name the covering expectation or expected-row cell, or state
|
|
82
|
+
`gap: <reason>`. Scan shared-attribute columns across transforms for cross-table agreement
|
|
83
|
+
obligations. Empty cells are honest; silent gaps are not.
|
|
84
|
+
9. **Prove the tests have teeth.** Once per test: corrupt one expected cell, `run`, confirm
|
|
85
|
+
`cell-mismatches` names exactly that column; revert. Then perturb one input cell and confirm
|
|
86
|
+
exactly the declared output cells move. Every `empty` expectation passes against an empty output,
|
|
87
|
+
so a green test can still be vacuous.
|
|
88
|
+
|
|
89
|
+
## Severity: error, or the tolerated oddity
|
|
90
|
+
|
|
91
|
+
Every expectation is pass or fail, and one failure fails the run. **error** = forbidden, and it
|
|
92
|
+
becomes an `empty` expectation. There is no warn severity, so **never author an expectation you
|
|
93
|
+
expect to fire**: a permanently red test trains everyone to ignore the result, and a red run gates
|
|
94
|
+
everyone else's work.
|
|
95
|
+
|
|
96
|
+
A tolerated-but-surfaced oddity — one the owner lives with, like orphan rows or ship-before-order
|
|
97
|
+
dates — gets encoded the two ways that hold: **pin it in the `equals` rows** (an orphan passing
|
|
98
|
+
through with NULLs is a cell in the expected output, so reversing the tolerance breaks the test),
|
|
99
|
+
and **record it in the plan's known-quirks list** with its real-data count, so the tolerance stays a
|
|
100
|
+
conscious choice.
|
|
101
|
+
|
|
102
|
+
Under budget pressure cut business-rule checks first, then cross-table structure checks; never
|
|
103
|
+
single-column screens (domains, ranges, nulls) — cheapest, and the last line.
|
|
104
|
+
|
|
105
|
+
## When a check exposes a live bug
|
|
106
|
+
|
|
107
|
+
Non-negotiable: **never soften the test to green** — expected values state correct behavior; matching
|
|
108
|
+
them to buggy output documents the bug as intended — and **surface the finding with its blast radius
|
|
109
|
+
at both scales**, fixture ("1530.24 of 1600.74 fixture dollars survive") and warehouse ("908 of
|
|
110
|
+
2,050 orders dropped"). What happens next follows the session's terms, not a fixed protocol: propose
|
|
111
|
+
and apply the fix now (when the user wants it or the autonomy setting covers it), or — when the fix
|
|
112
|
+
must wait — hold the correct expectation and record the red in the plan with the minimal fix body.
|
|
113
|
+
A stored test has no red-by-design state, so a deferred
|
|
114
|
+
fix must be visible in the plan or the test reads as broken. Either way, once green the test stays
|
|
115
|
+
as the regression guard.
|
|
116
|
+
|
|
117
|
+
## When the doctrine doesn't apply
|
|
118
|
+
|
|
119
|
+
The vocabulary follows Kimball's dimensional modeling (grain, additivity, conformed attributes,
|
|
120
|
+
slowly changing dimensions) — precise, widely understood terms. Real models are Kimball-inspired at
|
|
121
|
+
most; no check may score adherence:
|
|
122
|
+
|
|
123
|
+
- Full calendar date dimensions are rare. Never demand one; test date _semantics_ — ranges,
|
|
124
|
+
orderings, volatile derivations.
|
|
125
|
+
- Surrogate keys are doctrine, natural-key joins are practice. Test whichever key the model
|
|
126
|
+
declares; never flag natural-key joins.
|
|
127
|
+
- "No NULL FKs / no NULL attributes" is doctrine routinely dropped. Null policy is three independent
|
|
128
|
+
declarations — measures, attributes, FKs — each detected and confirmed, never presumed.
|
|
129
|
+
- One-big-table is legitimate: it still has a grain, its copies still need agreement checks, its
|
|
130
|
+
functional dependencies still hold.
|
|
131
|
+
- A transform-level `ORDER BY` has no testable effect — output tables carry no row order and the
|
|
132
|
+
`equals` comparison is a multiset. Flag it as probable dead weight (clustering hints aside); never
|
|
133
|
+
write an ordering expectation.
|
|
134
|
+
|
|
135
|
+
## One transform at a time
|
|
136
|
+
|
|
137
|
+
Each test covers one transform. When the transform under test reads another transform's output,
|
|
138
|
+
that target table is an input like any other: declare it and fake it. Derive those rows from the
|
|
139
|
+
base transform's own expected output, so the two tests tell one story, and note the coupling in both
|
|
140
|
+
plans — changing the base's expected rows means changing this test's input.
|
|
141
|
+
|
|
142
|
+
## Worked example, condensed
|
|
143
|
+
|
|
144
|
+
`orders` + `customers` → _enriched_orders_ (order grain; LEFT JOIN attaches `customer_name`,
|
|
145
|
+
`tier`).
|
|
146
|
+
|
|
147
|
+
Cast, 7 orders: two tiers; Alice and Carol with 2 orders each (multiplicity); two never shipped
|
|
148
|
+
(zero-case for the shipping join); order 106 shipped before ordered (real oddity, count cited);
|
|
149
|
+
order 107's customer_id matches no customer (orphan). Two `rows` inputs, one per source table.
|
|
150
|
+
|
|
151
|
+
Derived, per catalog: grain uniqueness on `order_id`; row and amount conservation from input to
|
|
152
|
+
output, as scalar subqueries over the target and the seeded input; `tier` domain ⊆ {standard,
|
|
153
|
+
premium}; orphan = keep-with-NULLs → an expected row pinning the NULL pass-through; ship-before-order
|
|
154
|
+
tolerated → the plan's known-quirks list with the warehouse count. The whole output pinned in one
|
|
155
|
+
`equals`, hand-computed, arithmetic in the plan. The inner-vs-LEFT-join bug this cast catches —
|
|
156
|
+
unshipped orders silently dropped from revenue — is what the zero-case rows exist for.
|
|
157
|
+
|
|
158
|
+
## Don't
|
|
159
|
+
|
|
160
|
+
- Don't author expectations before loading the `transform` skill — the body shape, the closed
|
|
161
|
+
create/update contract, and the verb flags live there.
|
|
162
|
+
- Don't capture expected rows from the transform's own output — hand-derive them or they assert
|
|
163
|
+
nothing.
|
|
164
|
+
- Don't emit checks for structure the model doesn't declare (snapshot density, version history,
|
|
165
|
+
bridge weights) — an inapplicable battery buries real findings.
|
|
166
|
+
- Don't let a fixture cast go all-clean — no zero-case, no orphan, no dirty row proves the happy
|
|
167
|
+
path and nothing else; the bugs live in the edges.
|
|
168
|
+
- Don't write an expectation you expect to fail.
|
|
169
|
+
- Don't surface bare check-ids ("per C3…") to the user — name the check in plain words; the ids are
|
|
170
|
+
for your cross-referencing, not their reading.
|