unspaghettit 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -10
- package/build/client/_app/immutable/assets/0.DFMDYAU9.css +1 -0
- package/build/client/_app/immutable/assets/0.DFMDYAU9.css.br +0 -0
- package/build/client/_app/immutable/assets/0.DFMDYAU9.css.gz +0 -0
- package/build/client/_app/immutable/assets/3.B3akUL9A.css +1 -0
- package/build/client/_app/immutable/assets/3.B3akUL9A.css.br +0 -0
- package/build/client/_app/immutable/assets/3.B3akUL9A.css.gz +0 -0
- package/build/client/_app/immutable/chunks/{ByNzXSTs.js → BO66rBOa2.js} +1 -1
- package/build/client/_app/immutable/chunks/BO66rBOa2.js.br +0 -0
- package/build/client/_app/immutable/chunks/BO66rBOa2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DsHardz9.js → BOrLmFwJ.js} +1 -1
- package/build/client/_app/immutable/chunks/BOrLmFwJ.js.br +0 -0
- package/build/client/_app/immutable/chunks/BOrLmFwJ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BPl9BITm.js +3 -0
- package/build/client/_app/immutable/chunks/BPl9BITm.js.br +0 -0
- package/build/client/_app/immutable/chunks/BPl9BITm.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BfMYiIUi.js +1 -0
- package/build/client/_app/immutable/chunks/BfMYiIUi.js.br +0 -0
- package/build/client/_app/immutable/chunks/BfMYiIUi.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Bp2_Vghh.js +1 -0
- package/build/client/_app/immutable/chunks/Bp2_Vghh.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bp2_Vghh.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{C8VYoO28.js → Bp8HpEPe2.js} +1 -1
- package/build/client/_app/immutable/chunks/Bp8HpEPe2.js.br +0 -0
- package/build/client/_app/immutable/chunks/{C8VYoO28.js.gz → Bp8HpEPe2.js.gz} +0 -0
- package/build/client/_app/immutable/chunks/C8rRKIXT.js +1 -0
- package/build/client/_app/immutable/chunks/C8rRKIXT.js.br +0 -0
- package/build/client/_app/immutable/chunks/C8rRKIXT.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{BsIqn_3K2.js → CJjJLwzv2.js} +1 -1
- package/build/client/_app/immutable/chunks/CJjJLwzv2.js.br +0 -0
- package/build/client/_app/immutable/chunks/CJjJLwzv2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Cl7Z3e6-.js +1 -0
- package/build/client/_app/immutable/chunks/Cl7Z3e6-.js.br +0 -0
- package/build/client/_app/immutable/chunks/Cl7Z3e6-.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CxyLMlpt2.js +2 -0
- package/build/client/_app/immutable/chunks/CxyLMlpt2.js.br +0 -0
- package/build/client/_app/immutable/chunks/CxyLMlpt2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D6jhrpTg.js +1 -0
- package/build/client/_app/immutable/chunks/D6jhrpTg.js.br +0 -0
- package/build/client/_app/immutable/chunks/D6jhrpTg.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D8YEa1po.js +1 -0
- package/build/client/_app/immutable/chunks/D8YEa1po.js.br +0 -0
- package/build/client/_app/immutable/chunks/D8YEa1po.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D9dKmajw.js +1 -0
- package/build/client/_app/immutable/chunks/D9dKmajw.js.br +0 -0
- package/build/client/_app/immutable/chunks/D9dKmajw.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DBJWcC6Y.js +1 -0
- package/build/client/_app/immutable/chunks/DBJWcC6Y.js.br +0 -0
- package/build/client/_app/immutable/chunks/DBJWcC6Y.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DHoA038D.js +1 -0
- package/build/client/_app/immutable/chunks/DHoA038D.js.br +2 -0
- package/build/client/_app/immutable/chunks/DHoA038D.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DKgYpi6_.js +1 -0
- package/build/client/_app/immutable/chunks/DKgYpi6_.js.br +0 -0
- package/build/client/_app/immutable/chunks/DKgYpi6_.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DatGSObE.js +1 -0
- package/build/client/_app/immutable/chunks/DatGSObE.js.br +0 -0
- package/build/client/_app/immutable/chunks/DatGSObE.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Dg0acPpT.js +1 -0
- package/build/client/_app/immutable/chunks/Dg0acPpT.js.br +0 -0
- package/build/client/_app/immutable/chunks/Dg0acPpT.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DjWKKtqp.js +1 -0
- package/build/client/_app/immutable/chunks/DjWKKtqp.js.br +1 -0
- package/build/client/_app/immutable/chunks/DjWKKtqp.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Dq0DUAz1.js +1 -0
- package/build/client/_app/immutable/chunks/Dq0DUAz1.js.br +0 -0
- package/build/client/_app/immutable/chunks/Dq0DUAz1.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DZ7_iXX8.js → HKLi1Yz3.js} +1 -1
- package/build/client/_app/immutable/chunks/HKLi1Yz3.js.br +0 -0
- package/build/client/_app/immutable/chunks/HKLi1Yz3.js.gz +0 -0
- package/build/client/_app/immutable/chunks/N7PXjlRH.js +1 -0
- package/build/client/_app/immutable/chunks/N7PXjlRH.js.br +0 -0
- package/build/client/_app/immutable/chunks/N7PXjlRH.js.gz +0 -0
- package/build/client/_app/immutable/chunks/aNMRV4sP2.js +5 -0
- package/build/client/_app/immutable/chunks/aNMRV4sP2.js.br +0 -0
- package/build/client/_app/immutable/chunks/aNMRV4sP2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/cMamJTsF.js +1 -0
- package/build/client/_app/immutable/chunks/cMamJTsF.js.br +0 -0
- package/build/client/_app/immutable/chunks/cMamJTsF.js.gz +0 -0
- package/build/client/_app/immutable/chunks/hpWJNn0t2.js +2 -0
- package/build/client/_app/immutable/chunks/hpWJNn0t2.js.br +0 -0
- package/build/client/_app/immutable/chunks/hpWJNn0t2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/iQu0D9Ux.js +1 -0
- package/build/client/_app/immutable/chunks/iQu0D9Ux.js.br +0 -0
- package/build/client/_app/immutable/chunks/iQu0D9Ux.js.gz +0 -0
- package/build/client/_app/immutable/chunks/ib21i2T_.js +6 -0
- package/build/client/_app/immutable/chunks/ib21i2T_.js.br +0 -0
- package/build/client/_app/immutable/chunks/ib21i2T_.js.gz +0 -0
- package/build/client/_app/immutable/chunks/suTxxv1t.js +1 -0
- package/build/client/_app/immutable/chunks/suTxxv1t.js.br +1 -0
- package/build/client/_app/immutable/chunks/suTxxv1t.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CLgh6Mx_.js +2 -0
- package/build/client/_app/immutable/entry/app.CLgh6Mx_.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CLgh6Mx_.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.D5GPCQZD.js +1 -0
- package/build/client/_app/immutable/entry/start.D5GPCQZD.js.br +0 -0
- package/build/client/_app/immutable/entry/start.D5GPCQZD.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.BOoI-hsu.js +4 -0
- package/build/client/_app/immutable/nodes/0.BOoI-hsu.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.BOoI-hsu.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.KYdA6ppX.js +1 -0
- package/build/client/_app/immutable/nodes/1.KYdA6ppX.js.br +2 -0
- package/build/client/_app/immutable/nodes/1.KYdA6ppX.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.DBz20KgG.js +1 -0
- package/build/client/_app/immutable/nodes/2.DBz20KgG.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DBz20KgG.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.19DIoFtw.js +1 -0
- package/build/client/_app/immutable/nodes/3.19DIoFtw.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.19DIoFtw.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.BvMzqBJj.js +3 -0
- package/build/client/_app/immutable/nodes/4.BvMzqBJj.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.BvMzqBJj.js.gz +0 -0
- package/build/client/_app/immutable/nodes/5.Dq6obSGG.js +42 -0
- package/build/client/_app/immutable/nodes/5.Dq6obSGG.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.Dq6obSGG.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.BOHISqs-.js +5 -0
- package/build/client/_app/immutable/nodes/6.BOHISqs-.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.BOHISqs-.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.CemgNJfw.js +1 -0
- package/build/client/_app/immutable/nodes/7.CemgNJfw.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.CemgNJfw.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.DejSfIYh.js +5 -0
- package/build/client/_app/immutable/nodes/8.DejSfIYh.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.DejSfIYh.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{8.l6ZO_YE3.js → 9.CMW6a2Lg.js} +51 -48
- package/build/client/_app/immutable/nodes/9.CMW6a2Lg.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.CMW6a2Lg.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/server/chunks/0-C_o0oz-N.js +15 -0
- package/build/server/chunks/0-C_o0oz-N.js.map +1 -0
- package/build/server/chunks/1-DPpKAKXV.js +9 -0
- package/build/server/chunks/{1-58bNZ9nH.js.map → 1-DPpKAKXV.js.map} +1 -1
- package/build/server/chunks/2-AlfFqtL1.js +9 -0
- package/build/server/chunks/2-AlfFqtL1.js.map +1 -0
- package/build/server/chunks/3-vbjUt_51.js +16 -0
- package/build/server/chunks/3-vbjUt_51.js.map +1 -0
- package/build/server/chunks/4-BNow4x6D.js +16 -0
- package/build/server/chunks/4-BNow4x6D.js.map +1 -0
- package/build/server/chunks/5-D7OCJpxD.js +9 -0
- package/build/server/chunks/5-D7OCJpxD.js.map +1 -0
- package/build/server/chunks/6-QQ7r8Rd5.js +9 -0
- package/build/server/chunks/6-QQ7r8Rd5.js.map +1 -0
- package/build/server/chunks/7-CbPLGaIG.js +9 -0
- package/build/server/chunks/7-CbPLGaIG.js.map +1 -0
- package/build/server/chunks/8-CVO-E-sf.js +9 -0
- package/build/server/chunks/8-CVO-E-sf.js.map +1 -0
- package/build/server/chunks/9-DxT1baO5.js +9 -0
- package/build/server/chunks/9-DxT1baO5.js.map +1 -0
- package/build/server/chunks/{FeatureCard-CDB0iQEO.js → FeatureCard-BQOY6gJQ.js} +2 -2
- package/build/server/chunks/{FeatureCard-CDB0iQEO.js.map → FeatureCard-BQOY6gJQ.js.map} +1 -1
- package/build/server/chunks/{FeatureJson-BKCG8vk2.js → FeatureJson-Bv-qiOSf.js} +2 -2
- package/build/server/chunks/{FeatureJson-BKCG8vk2.js.map → FeatureJson-Bv-qiOSf.js.map} +1 -1
- package/build/server/chunks/FeatureTransforms-UWDHmWEg.js +311 -0
- package/build/server/chunks/FeatureTransforms-UWDHmWEg.js.map +1 -0
- package/build/server/chunks/{FeatureValidator-DDdFIzr8.js → FeatureValidator-B8qEjAUW.js} +235 -3
- package/build/server/chunks/FeatureValidator-B8qEjAUW.js.map +1 -0
- package/build/server/chunks/{ManageTagsDialog-CT8-t_GJ.js → ManageTagsDialog-DBxA3tM2.js} +4 -3
- package/build/server/chunks/ManageTagsDialog-DBxA3tM2.js.map +1 -0
- package/build/server/chunks/{ProjectsIndex-yg-CEhxx.js → ProjectsIndex-CoDrvRya.js} +8 -8
- package/build/server/chunks/ProjectsIndex-CoDrvRya.js.map +1 -0
- package/build/server/chunks/{RenameTag-nSZ6Leh-.js → RenameTag-BuWLMl1m.js} +2 -2
- package/build/server/chunks/{RenameTag-nSZ6Leh-.js.map → RenameTag-BuWLMl1m.js.map} +1 -1
- package/build/server/chunks/TagDotStrip-B1XQ0m0Y.js +88 -0
- package/build/server/chunks/TagDotStrip-B1XQ0m0Y.js.map +1 -0
- package/build/server/chunks/{TagFilterSelect-BlEttqQE.js → TagFilterSelect-DbO0Qduh.js} +4 -3
- package/build/server/chunks/TagFilterSelect-DbO0Qduh.js.map +1 -0
- package/build/server/chunks/{TagPalette-CvQJ40bt.js → TagPalette-CtMNYCmu.js} +6 -2
- package/build/server/chunks/TagPalette-CtMNYCmu.js.map +1 -0
- package/build/server/chunks/{_layout.svelte-D2SACu8G.js → _layout.svelte-BREws55o.js} +99 -37
- package/build/server/chunks/_layout.svelte-BREws55o.js.map +1 -0
- package/build/server/chunks/{_page.svelte-Dbu42LbU.js → _page.svelte-B1s7jxaQ.js} +9 -8
- package/build/server/chunks/{_page.svelte-Dbu42LbU.js.map → _page.svelte-B1s7jxaQ.js.map} +1 -1
- package/build/server/chunks/_page.svelte-B7hT3P8E.js +29 -0
- package/build/server/chunks/{_page.svelte-CsqELhKQ.js.map → _page.svelte-B7hT3P8E.js.map} +1 -1
- package/build/server/chunks/{_page.svelte-DYOnk7iP.js → _page.svelte-BKKCa9H5.js} +11 -8
- package/build/server/chunks/{_page.svelte-DYOnk7iP.js.map → _page.svelte-BKKCa9H5.js.map} +1 -1
- package/build/server/chunks/{_page.svelte-C2jPZylo.js → _page.svelte-BKTveFAj.js} +16 -14
- package/build/server/chunks/_page.svelte-BKTveFAj.js.map +1 -0
- package/build/server/chunks/_page.svelte-BqSC-1vK.js +404 -0
- package/build/server/chunks/_page.svelte-BqSC-1vK.js.map +1 -0
- package/build/server/chunks/{_page.svelte-bbz79XAC.js → _page.svelte-BtI2zZ_Z.js} +38 -282
- package/build/server/chunks/_page.svelte-BtI2zZ_Z.js.map +1 -0
- package/build/server/chunks/_page.svelte-De508ek8.js +29 -0
- package/build/server/chunks/{_page.svelte-C3FIXVWr.js.map → _page.svelte-De508ek8.js.map} +1 -1
- package/build/server/chunks/{_page.svelte-BUfFGiJQ.js → _page.svelte-Zf9H4KOP.js} +16 -14
- package/build/server/chunks/_page.svelte-Zf9H4KOP.js.map +1 -0
- package/build/server/chunks/{_server.ts-Bs4QqmRJ.js → _server.ts-0uNTZKwF.js} +7 -6
- package/build/server/chunks/{_server.ts-Bs4QqmRJ.js.map → _server.ts-0uNTZKwF.js.map} +1 -1
- package/build/server/chunks/{_server.ts-Bnpe4TIM.js → _server.ts-BIBWjQI-.js} +5 -4
- package/build/server/chunks/{_server.ts-Bnpe4TIM.js.map → _server.ts-BIBWjQI-.js.map} +1 -1
- package/build/server/chunks/{_server.ts-FARjojeH.js → _server.ts-BihpbxOW.js} +5 -4
- package/build/server/chunks/{_server.ts-FARjojeH.js.map → _server.ts-BihpbxOW.js.map} +1 -1
- package/build/server/chunks/{_server.ts-efMdTu3p.js → _server.ts-BuoQ02BW.js} +5 -4
- package/build/server/chunks/{_server.ts-efMdTu3p.js.map → _server.ts-BuoQ02BW.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DDtoQw0L.js → _server.ts-CWvU15tL.js} +5 -4
- package/build/server/chunks/{_server.ts-DDtoQw0L.js.map → _server.ts-CWvU15tL.js.map} +1 -1
- package/build/server/chunks/{_server.ts-B80OMNjj.js → _server.ts-D4EO0VHu.js} +6 -5
- package/build/server/chunks/{_server.ts-B80OMNjj.js.map → _server.ts-D4EO0VHu.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DZi40j_b.js → _server.ts-DDa0Nk_S.js} +5 -4
- package/build/server/chunks/{_server.ts-DZi40j_b.js.map → _server.ts-DDa0Nk_S.js.map} +1 -1
- package/build/server/chunks/{_server.ts-BaT7ADxX.js → _server.ts-DcZPhyhK.js} +7 -6
- package/build/server/chunks/{_server.ts-BaT7ADxX.js.map → _server.ts-DcZPhyhK.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CMPK37Vt.js → _server.ts-DsbbVoOn.js} +5 -4
- package/build/server/chunks/{_server.ts-CMPK37Vt.js.map → _server.ts-DsbbVoOn.js.map} +1 -1
- package/build/server/chunks/{_server.ts-y0yINgGA.js → _server.ts-Dy5zQHe2.js} +8 -7
- package/build/server/chunks/{_server.ts-y0yINgGA.js.map → _server.ts-Dy5zQHe2.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CagU2lG4.js → _server.ts-YnfXkeMJ.js} +8 -7
- package/build/server/chunks/_server.ts-YnfXkeMJ.js.map +1 -0
- package/build/server/chunks/{_server.ts-RzdFK98M.js → _server.ts-vcaqdwxS.js} +5 -4
- package/build/server/chunks/_server.ts-vcaqdwxS.js.map +1 -0
- package/build/server/chunks/{browserContainer-D4Rbh5md.js → browserContainer-CNX7ANld.js} +115 -30
- package/build/server/chunks/browserContainer-CNX7ANld.js.map +1 -0
- package/build/server/chunks/builderModeStore.svelte-ihupr-3p.js +681 -0
- package/build/server/chunks/builderModeStore.svelte-ihupr-3p.js.map +1 -0
- package/build/server/chunks/{client-DzD76Kll.js → client-DfpLcAZ9.js} +2 -2
- package/build/server/chunks/{client-DzD76Kll.js.map → client-DfpLcAZ9.js.map} +1 -1
- package/build/server/chunks/{error.svelte-BYvfCaDL.js → error.svelte-C35KOpru.js} +4 -4
- package/build/server/chunks/{error.svelte-BYvfCaDL.js.map → error.svelte-C35KOpru.js.map} +1 -1
- package/build/server/chunks/eventBus-B9k-Vzso.js +19 -0
- package/build/server/chunks/eventBus-B9k-Vzso.js.map +1 -0
- package/build/server/chunks/{featuresStore.svelte-C5UcVnjA.js → featuresStore.svelte-DR1gYZyu.js} +6 -5
- package/build/server/chunks/featuresStore.svelte-DR1gYZyu.js.map +1 -0
- package/build/server/chunks/{internal-BA0a_nCh.js → internal-BPKrFkK1.js} +2 -2
- package/build/server/chunks/{internal-BA0a_nCh.js.map → internal-BPKrFkK1.js.map} +1 -1
- package/build/server/chunks/{projectsStore.svelte-CCIKC4cA.js → projectsStore.svelte-QDz5PchX.js} +3 -2
- package/build/server/chunks/projectsStore.svelte-QDz5PchX.js.map +1 -0
- package/build/server/chunks/{reconcile-CYtw0vAj.js → reconcile-Dv7jS3C8.js} +3 -3
- package/build/server/chunks/{reconcile-CYtw0vAj.js.map → reconcile-Dv7jS3C8.js.map} +1 -1
- package/build/server/chunks/shared-server-CCs2CV3b.js +14 -0
- package/build/server/chunks/shared-server-CCs2CV3b.js.map +1 -0
- package/build/server/chunks/{snapshotRepository-D04sCQ0-.js → snapshotRepository-BPXCjX92.js} +40 -8
- package/build/server/chunks/snapshotRepository-BPXCjX92.js.map +1 -0
- package/build/server/chunks/{state-BVel4q-J.js → state-CpLVNZq7.js} +2 -2
- package/build/server/chunks/{state-BVel4q-J.js.map → state-CpLVNZq7.js.map} +1 -1
- package/build/server/chunks/{sync-BFBTG9bE.js → sync-DZ3dn761.js} +2 -2
- package/build/server/chunks/{sync-BFBTG9bE.js.map → sync-DZ3dn761.js.map} +1 -1
- package/build/server/chunks/{syncBridge-B90NwG8R.js → syncBridge-BwXzPdjJ.js} +2 -2
- package/build/server/chunks/{syncBridge-B90NwG8R.js.map → syncBridge-BwXzPdjJ.js.map} +1 -1
- package/build/server/chunks/{TagDotStrip-K9ispK6D.js → tagPaletteStore.svelte-DfZOBvgY.js} +5 -87
- package/build/server/chunks/tagPaletteStore.svelte-DfZOBvgY.js.map +1 -0
- package/build/server/chunks/{tourStore.svelte-C2hizkdG.js → tourStore.svelte-CYs4oLjF.js} +2 -2
- package/build/server/chunks/tourStore.svelte-CYs4oLjF.js.map +1 -0
- package/build/server/index.js +2 -12
- package/build/server/index.js.map +1 -1
- package/build/server/manifest.js +36 -28
- package/build/server/manifest.js.map +1 -1
- package/cli/README.md +46 -20
- package/cli/clients/claude-code.ts +5 -4
- package/cli/clients/claude-desktop.ts +5 -4
- package/cli/commands/dashboard.ts +38 -4
- package/cli/commands/init.ts +147 -84
- package/cli/commands/view.ts +77 -0
- package/cli/skills/unspa-audit/SKILL.md +9 -0
- package/cli/skills/unspa-edit/SKILL.md +91 -0
- package/cli/skills/unspa-implement/SKILL.md +46 -1
- package/cli/unspa.ts +57 -13
- package/cli/util/context-files.ts +5 -3
- package/cli/util/views.ts +38 -0
- package/mcp-server/resources/operations.ts +2 -1
- package/mcp-server/tools/_evolution.ts +53 -0
- package/mcp-server/tools/action.ts +64 -6
- package/mcp-server/tools/batch.ts +34 -24
- package/mcp-server/tools/feature.ts +17 -9
- package/mcp-server/tools/queue.ts +101 -9
- package/mcp-server/tools/specGaps.ts +14 -1
- package/package.json +3 -1
- package/src/features/behavior-model/application/use-cases/MutateFeature.ts +20 -31
- package/src/features/behavior-model/application/use-cases/SaveFeature.ts +13 -9
- package/src/features/behavior-model/domain/entities/Action.ts +167 -95
- package/src/features/behavior-model/domain/services/FeatureTransforms.ts +49 -0
- package/src/features/behavior-model/domain/services/FeatureValidator.ts +50 -0
- package/src/features/behavior-model/infrastructure/persistence/snapshot-discovery.ts +28 -3
- package/src/features/behavior-model/presentation/components/ActionEditor.svelte +55 -7
- package/src/features/builder-mode/application/use-cases/GetBuilderModeDashboard.ts +41 -0
- package/src/features/builder-mode/application/use-cases/GetBuilderModeProject.ts +42 -0
- package/src/features/builder-mode/domain/BuilderModeDashboard.ts +172 -0
- package/src/features/builder-mode/infrastructure/adapters/defaultBuilderHost.ts +27 -0
- package/src/features/builder-mode/presentation/components/BuilderModeDashboard.svelte +1335 -0
- package/src/features/builder-mode/presentation/components/BuilderTagChips.svelte +289 -0
- package/src/features/builder-mode/presentation/components/ClampedDescription.svelte +174 -0
- package/src/features/builder-mode/presentation/components/GameScore.svelte +194 -0
- package/src/features/builder-mode/presentation/ports/BuilderHostPorts.ts +39 -0
- package/src/features/builder-mode/presentation/stores/builderModeStore.svelte.ts +679 -0
- package/src/features/implementation-queue/application/use-cases/EnqueueQueueItem.ts +16 -5
- package/src/features/implementation-queue/application/use-cases/SetQueueItemTarget.ts +38 -0
- package/src/features/implementation-queue/domain/entities/QueueItem.ts +43 -0
- package/src/features/implementation-queue/domain/services/QueueOperations.ts +26 -1
- package/src/features/maturity/domain/MaturityScorer.ts +13 -4
- package/src/features/mcp-tools/application/tools/listActions.ts +9 -1
- package/src/features/projects/infrastructure/io/ProjectJson.ts +25 -4
- package/src/lib/views/enabled.ts +19 -0
- package/src/lib/views/registry.ts +72 -0
- package/src/routes/+layout.svelte +583 -348
- package/src/routes/builder-mode/+page.svelte +9 -0
- package/src/routes/builder-mode/+page.ts +12 -0
- package/src/routes/tutorial/+page.svelte +8 -0
- package/src/shared/infrastructure/container.ts +208 -199
- package/build/client/_app/immutable/assets/0.BgNAkMzG.css +0 -1
- package/build/client/_app/immutable/assets/0.BgNAkMzG.css.br +0 -0
- package/build/client/_app/immutable/assets/0.BgNAkMzG.css.gz +0 -0
- package/build/client/_app/immutable/chunks/8V0Jan-K.js +0 -2
- package/build/client/_app/immutable/chunks/8V0Jan-K.js.br +0 -0
- package/build/client/_app/immutable/chunks/8V0Jan-K.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B5jHfatx.js +0 -1
- package/build/client/_app/immutable/chunks/B5jHfatx.js.br +0 -0
- package/build/client/_app/immutable/chunks/B5jHfatx.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BQQLoxQo.js +0 -5
- package/build/client/_app/immutable/chunks/BQQLoxQo.js.br +0 -0
- package/build/client/_app/immutable/chunks/BQQLoxQo.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BZCGqXF-.js +0 -1
- package/build/client/_app/immutable/chunks/BZCGqXF-.js.br +0 -2
- package/build/client/_app/immutable/chunks/BZCGqXF-.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BpZHquM3.js +0 -1
- package/build/client/_app/immutable/chunks/BpZHquM3.js.br +0 -0
- package/build/client/_app/immutable/chunks/BpZHquM3.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BsIqn_3K2.js.br +0 -0
- package/build/client/_app/immutable/chunks/BsIqn_3K2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BxAqshUk.js +0 -1
- package/build/client/_app/immutable/chunks/BxAqshUk.js.br +0 -0
- package/build/client/_app/immutable/chunks/BxAqshUk.js.gz +0 -0
- package/build/client/_app/immutable/chunks/ByNzXSTs.js.br +0 -0
- package/build/client/_app/immutable/chunks/ByNzXSTs.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C0OSZ2OV.js +0 -1
- package/build/client/_app/immutable/chunks/C0OSZ2OV.js.br +0 -0
- package/build/client/_app/immutable/chunks/C0OSZ2OV.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C2XQCtR3.js +0 -1
- package/build/client/_app/immutable/chunks/C2XQCtR3.js.br +0 -0
- package/build/client/_app/immutable/chunks/C2XQCtR3.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C8VYoO28.js.br +0 -0
- package/build/client/_app/immutable/chunks/CKUA0lwj.js +0 -1
- package/build/client/_app/immutable/chunks/CKUA0lwj.js.br +0 -0
- package/build/client/_app/immutable/chunks/CKUA0lwj.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CM5qK71w.js +0 -6
- package/build/client/_app/immutable/chunks/CM5qK71w.js.br +0 -0
- package/build/client/_app/immutable/chunks/CM5qK71w.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CiC9AlkQ.js +0 -1
- package/build/client/_app/immutable/chunks/CiC9AlkQ.js.br +0 -0
- package/build/client/_app/immutable/chunks/CiC9AlkQ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DF6QYH3p.js +0 -3
- package/build/client/_app/immutable/chunks/DF6QYH3p.js.br +0 -0
- package/build/client/_app/immutable/chunks/DF6QYH3p.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DGo7EzwK.js +0 -1
- package/build/client/_app/immutable/chunks/DGo7EzwK.js.br +0 -0
- package/build/client/_app/immutable/chunks/DGo7EzwK.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DOrHTzKX2.js +0 -2
- package/build/client/_app/immutable/chunks/DOrHTzKX2.js.br +0 -0
- package/build/client/_app/immutable/chunks/DOrHTzKX2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DZ7_iXX8.js.br +0 -0
- package/build/client/_app/immutable/chunks/DZ7_iXX8.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DayeafKd.js +0 -1
- package/build/client/_app/immutable/chunks/DayeafKd.js.br +0 -0
- package/build/client/_app/immutable/chunks/DayeafKd.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DsHardz9.js.br +0 -1
- package/build/client/_app/immutable/chunks/DsHardz9.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Dvd8XBNO.js +0 -1
- package/build/client/_app/immutable/chunks/Dvd8XBNO.js.br +0 -1
- package/build/client/_app/immutable/chunks/Dvd8XBNO.js.gz +0 -0
- package/build/client/_app/immutable/chunks/lYfEZr7V.js +0 -1
- package/build/client/_app/immutable/chunks/lYfEZr7V.js.br +0 -0
- package/build/client/_app/immutable/chunks/lYfEZr7V.js.gz +0 -0
- package/build/client/_app/immutable/chunks/nx-Rqoe9.js +0 -1
- package/build/client/_app/immutable/chunks/nx-Rqoe9.js.br +0 -0
- package/build/client/_app/immutable/chunks/nx-Rqoe9.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CBQ5lc4C.js +0 -2
- package/build/client/_app/immutable/entry/app.CBQ5lc4C.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CBQ5lc4C.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.CwosiFCq.js +0 -1
- package/build/client/_app/immutable/entry/start.CwosiFCq.js.br +0 -0
- package/build/client/_app/immutable/entry/start.CwosiFCq.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.CrNDTsZU.js +0 -3
- package/build/client/_app/immutable/nodes/0.CrNDTsZU.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.CrNDTsZU.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.DIhR9Se9.js +0 -1
- package/build/client/_app/immutable/nodes/1.DIhR9Se9.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.DIhR9Se9.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.Cxe9W2ys.js +0 -1
- package/build/client/_app/immutable/nodes/2.Cxe9W2ys.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.Cxe9W2ys.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.Db_qGhZh.js +0 -3
- package/build/client/_app/immutable/nodes/3.Db_qGhZh.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.Db_qGhZh.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.CNlfoebz.js +0 -42
- package/build/client/_app/immutable/nodes/4.CNlfoebz.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.CNlfoebz.js.gz +0 -0
- package/build/client/_app/immutable/nodes/5.52vWUOmx.js +0 -5
- package/build/client/_app/immutable/nodes/5.52vWUOmx.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.52vWUOmx.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.DEa_tOGv.js +0 -1
- package/build/client/_app/immutable/nodes/6.DEa_tOGv.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.DEa_tOGv.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.Bj0xztLC.js +0 -5
- package/build/client/_app/immutable/nodes/7.Bj0xztLC.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.Bj0xztLC.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.l6ZO_YE3.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.l6ZO_YE3.js.gz +0 -0
- package/build/server/chunks/0-BpC7BfBT.js +0 -15
- package/build/server/chunks/0-BpC7BfBT.js.map +0 -1
- package/build/server/chunks/1-58bNZ9nH.js +0 -9
- package/build/server/chunks/2-DF_NN0lE.js +0 -9
- package/build/server/chunks/2-DF_NN0lE.js.map +0 -1
- package/build/server/chunks/3-NRtKEkC_.js +0 -16
- package/build/server/chunks/3-NRtKEkC_.js.map +0 -1
- package/build/server/chunks/4-MZJkDNx2.js +0 -9
- package/build/server/chunks/4-MZJkDNx2.js.map +0 -1
- package/build/server/chunks/5-Lq13lWK3.js +0 -9
- package/build/server/chunks/5-Lq13lWK3.js.map +0 -1
- package/build/server/chunks/6-DqZ3Anew.js +0 -9
- package/build/server/chunks/6-DqZ3Anew.js.map +0 -1
- package/build/server/chunks/7-BOAHc6Hs.js +0 -9
- package/build/server/chunks/7-BOAHc6Hs.js.map +0 -1
- package/build/server/chunks/8-DBHj3VAe.js +0 -9
- package/build/server/chunks/8-DBHj3VAe.js.map +0 -1
- package/build/server/chunks/FeatureValidator-DDdFIzr8.js.map +0 -1
- package/build/server/chunks/ManageTagsDialog-CT8-t_GJ.js.map +0 -1
- package/build/server/chunks/ProjectsIndex-yg-CEhxx.js.map +0 -1
- package/build/server/chunks/TagDotStrip-K9ispK6D.js.map +0 -1
- package/build/server/chunks/TagFilterSelect-BlEttqQE.js.map +0 -1
- package/build/server/chunks/TagPalette-CvQJ40bt.js.map +0 -1
- package/build/server/chunks/_layout.svelte-D2SACu8G.js.map +0 -1
- package/build/server/chunks/_page.svelte-BUfFGiJQ.js.map +0 -1
- package/build/server/chunks/_page.svelte-C2jPZylo.js.map +0 -1
- package/build/server/chunks/_page.svelte-C3FIXVWr.js +0 -27
- package/build/server/chunks/_page.svelte-CsqELhKQ.js +0 -27
- package/build/server/chunks/_page.svelte-bbz79XAC.js.map +0 -1
- package/build/server/chunks/_server.ts-CagU2lG4.js.map +0 -1
- package/build/server/chunks/_server.ts-RzdFK98M.js.map +0 -1
- package/build/server/chunks/browserContainer-D4Rbh5md.js.map +0 -1
- package/build/server/chunks/featuresStore.svelte-C5UcVnjA.js.map +0 -1
- package/build/server/chunks/projectsStore.svelte-CCIKC4cA.js.map +0 -1
- package/build/server/chunks/snapshotRepository-D04sCQ0-.js.map +0 -1
- package/build/server/chunks/tourStore.svelte-C2hizkdG.js.map +0 -1
- /package/build/client/_app/immutable/assets/{8.nv0I59TU.css → 9.nv0I59TU.css} +0 -0
- /package/build/client/_app/immutable/assets/{8.nv0I59TU.css.br → 9.nv0I59TU.css.br} +0 -0
- /package/build/client/_app/immutable/assets/{8.nv0I59TU.css.gz → 9.nv0I59TU.css.gz} +0 -0
- /package/build/client/_app/immutable/chunks/{BQ9GodWX.js → BQ9GodWX2.js} +0 -0
- /package/build/client/_app/immutable/chunks/{BQ9GodWX.js.br → BQ9GodWX2.js.br} +0 -0
- /package/build/client/_app/immutable/chunks/{BQ9GodWX.js.gz → BQ9GodWX2.js.gz} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_t as e,et as t,tt as n,z as r}from"./BPl9BITm.js";var i=new class{#e=n(null);get current(){return r(this.#e)}set current(e){t(this.#e,e,!0)}confirm(e){return new Promise(t=>{this.current={kind:`confirm`,opts:e,resolve:t}})}alert(e){return new Promise(t=>{this.current={kind:`alert`,opts:e,resolve:t}})}checklist(e){return new Promise(t=>{this.current={kind:`checklist`,opts:e,resolve:t}})}choose(e){return new Promise(t=>{this.current={kind:`choice`,opts:e,resolve:t}})}prompt(e){return new Promise(t=>{this.current={kind:`prompt`,opts:e,resolve:t}})}resolveCurrent(e,t=[]){if(!this.current)return;let n=this.current;this.current=null,n.kind===`confirm`?n.resolve(e):n.kind===`alert`?n.resolve():n.kind===`checklist`?n.resolve({confirmed:e,selectedIds:t}):n.resolve(null)}resolveChoice(e){if(!this.current||this.current.kind!==`choice`)return;let t=this.current;this.current=null,t.resolve(e)}resolvePrompt(e){if(!this.current||this.current.kind!==`prompt`)return;let t=this.current;this.current=null,t.resolve(e)}},a=e=>i.confirm(e),o=e=>i.alert(e),s=e=>i.checklist(e),c=e=>i.choose(e),l=e=>i.prompt(e),u=`unspa.auth.token`,d=new class{#e=n(``);get token(){return r(this.#e)}set token(e){t(this.#e,e,!0)}initialized=!1;init(){if(this.initialized||(this.initialized=!0,typeof localStorage>`u`))return;let e=localStorage.getItem(u);e&&(this.token=e)}setToken(e){let t=e.trim();this.token=t,!(typeof localStorage>`u`)&&(t.length>0?localStorage.setItem(u,t):localStorage.removeItem(u))}clear(){this.setToken(``)}get hasToken(){return this.token.length>0}},f=(e,t)=>{let n=new Headers(e?.headers);return t.length>0&&n.set(`Authorization`,`Bearer ${t}`),n},p=null,m=async e=>{if(p)return p;p=l({title:`Dashboard access token required`,message:`${e}\n\nAsk the admin who started this dashboard for the value of UNSPA_AUTH_TOKEN.`,inputLabel:`Access token`,password:!0,confirmLabel:`Sign in`,tone:`info`});try{return await p}finally{p=null}},h=async(e,t)=>{let n=n=>fetch(e,{...t,headers:f(t,n)}),r=d.token,i=await n(r);if(i.status!==401)return i;let a=await m(r.length===0?`This dashboard is gated by a shared access token.`:`The stored access token was rejected. The admin may have rotated it.`);return a===null||a.length===0?i:(d.setToken(a),n(a))},g=`/api/snapshots`,_=e=>`${g}/${encodeURIComponent(e)}`,v=class{async list(){let e=await h(g);if(!e.ok)throw Error(`Snapshot list failed (${e.status})`);return(await e.json()).summaries}async get(e){let t=await h(_(e));if(t.status===404)return null;if(!t.ok)throw Error(`Snapshot get failed (${t.status})`);return await t.json()}async save(e){let t=await h(_(e.id),{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e)});if(!t.ok){let e=await t.text().catch(()=>``);throw Error(`Snapshot save failed (${t.status})${e?`: ${e}`:``}`)}}async delete(e){let t=await h(_(e),{method:`DELETE`});if(!t.ok&&t.status!==404)throw Error(`Snapshot delete failed (${t.status})`)}},y=e=>e.trim().toLowerCase(),b=(e,t)=>{let n=new Map;for(let t of e??[]){let e=y(t.type),r=y(t.value);!e||!r||n.set(`${e}:${r}`,{type:e,value:r})}let r=y(t?.value??``);if(r){let e=y(t?.type??``)||`tag`;n.set(`${e}:${r}`,{type:e,value:r})}let i=[...n.values()];return i.length>0?i:void 0},ee=(e,t)=>e&&e.some(e=>x(e)===x(t))?e:b([...e??[],t]),te=(e,t)=>{if(!e||e.length===0)return;let n=x(t),r=e.filter(e=>x(e)!==n);return r.length>0?r:void 0},ne=e=>{let t=e.trim();return t?t.split(/(\s+|[-_])/).map(e=>e.length===0?e:/^\s+$/.test(e)||e===`-`||e===`_`?` `:e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(``):``},re=e=>`${ne(e.type)}: ${ne(e.value)}`,x=e=>`${e.type.trim().toLowerCase()}:${e.value.trim().toLowerCase()}`,ie=(e,t,n)=>{if(!e||e.length===0)return e;let r=x(t),i=!1,a=e.map(e=>x(e)===r?(i=!0,n):e);return i?b(a):e},ae=[],oe=e({default:()=>le,feature:()=>ce,format:()=>se,version:()=>1}),se=`unspaghettit`,ce=JSON.parse(`{"id":"941e4158","name":"Account & auth","description":"Sign up, sign in, and verify email. Locks out repeated failures, caps verification retries, and sends a welcome email after signup.","surfaces":[{"id":"86fcdef9","name":"Sign up","type":"screen","description":"Account creation form. Validates the buyer's input, blocks duplicates, and hands off to email verification.","stateDefinitions":[{"id":"d209fd87","path":"user.authenticated","type":"boolean","defaultValue":false,"description":"Whether the visitor is signed in.","sharedWith":["493e1030","ab24f04f"]},{"id":"2db43325","path":"user.exists","type":"boolean","defaultValue":false,"description":"Whether the email already has an account.","sharedWith":["493e1030"]},{"id":"6428c5f0","path":"user.emailVerified","type":"boolean","defaultValue":false,"description":"Whether the user's email has been verified.","sharedWith":["ab24f04f"]},{"id":"319bd866","path":"form.emailValid","type":"boolean","defaultValue":true,"description":"Result of the client-side email format check."},{"id":"9032b8fa","path":"form.passwordStrong","type":"boolean","defaultValue":true,"description":"Whether the proposed password passes the strength meter."},{"id":"5323156a","path":"form.termsAccepted","type":"boolean","defaultValue":false,"description":"Whether the buyer accepted the Terms of Service."}],"actions":[{"id":"b336163d","name":"Create account","intent":"Create a new account for a first-time visitor.","parameters":[{"id":"1f033700","name":"email","type":"string","required":true,"description":"Email address for the new account."},{"id":"14a13b71","name":"password","type":"string","required":true,"description":"Proposed password; the strength meter writes form.passwordStrong."},{"id":"87abdd09","name":"acceptTerms","type":"boolean","required":true,"description":"Whether the buyer ticked the Terms checkbox. Bound to form.termsAccepted.","defaultValue":false,"bindToStatePath":"form.termsAccepted"}],"requiredStates":["form.emailValid","form.passwordStrong","form.termsAccepted","user.exists"],"rules":[{"id":"61136c8a","category":"validation","condition":{"kind":"not","condition":{"kind":"all","conditions":[{"left":"form.emailValid","operator":"is_true"},{"left":"form.passwordStrong","operator":"is_true"},{"left":"form.termsAccepted","operator":"is_true"}]}},"effect":{"type":"block_action","reason":"Complete every required field before creating an account.","id":"9c5798b8","description":"Effect block_action from a validation rule on action Create account."},"description":"Composite NOT: block until every form check passes. One rule replaces three flat ones."},{"id":"ee2466e9","category":"security","condition":{"left":"user.exists","operator":"is_true"},"effect":{"type":"block_action","reason":"An account with that email exists. Sign in instead.","id":"a60124df","description":"Effect block_action from a security rule on action Create account."},"description":"Security rule on action Create account."},{"id":"2a4bfa4d","category":"business","condition":{"left":"user.authenticated","operator":"is_true"},"effect":{"type":"block_action","reason":"You are already signed in.","id":"c070af8b","description":"Effect block_action from a business rule on action Create account."},"description":"Business rule on action Create account."}],"invariants":[],"effects":[{"type":"set_state","path":"user.authenticated","value":true,"id":"43358ff6","description":"Effect set_state on action Create account."},{"type":"set_state","path":"user.exists","value":true,"id":"5bb8fad9","description":"Effect set_state on action Create account."},{"type":"emit_event","event":"account.signup.completed","id":"7e87b898","description":"Effect emit_event on action Create account."},{"type":"show_message","message":"Account created. Check your email for the verification code.","id":"e8645f9e","description":"Effect show_message on action Create account."},{"type":"transition_surface","target":"ab24f04f","id":"d245def4","description":"Effect transition_surface on action Create account."}],"emittedEvents":["account.signup.requested","account.signup.completed","account.signup.blocked"],"transitions":[],"scenarios":[{"id":"1d6ac381","name":"Happy path: new visitor signs up","description":"Form valid, no existing account.","stateOverrides":[{"path":"form.termsAccepted","value":true},{"path":"user.exists","value":false},{"path":"user.authenticated","value":false}],"parameterOverrides":[{"parameterName":"email","value":"ada@example.com"},{"parameterName":"password","value":"StrongPass1!"},{"parameterName":"acceptTerms","value":true}],"expectedStatus":"success","expectedAssertions":[{"path":"user.authenticated","operator":"is_true","description":"Asserts user.authenticated is_true."},{"path":"user.exists","operator":"is_true","description":"Asserts user.exists is_true."}],"expectedTransition":"ab24f04f"},{"id":"ef8d7c77","name":"Blocked: terms not accepted","description":"Composite NOT rule trips when any form check fails.","stateOverrides":[{"path":"form.termsAccepted","value":false}],"parameterOverrides":[{"parameterName":"email","value":"ada@example.com"},{"parameterName":"password","value":"StrongPass1!"},{"parameterName":"acceptTerms","value":false}],"expectedStatus":"blocked"}]},{"id":"450141bc","name":"Go to sign in","intent":"Navigate to the sign-in surface for an existing account.","parameters":[],"requiredStates":[],"rules":[{"id":"fad5bb4b","category":"business","condition":{"left":"user.authenticated","operator":"is_true"},"effect":{"type":"block_action","reason":"You are already signed in.","id":"b36283a4","description":"Effect block_action from a business rule on action Go to sign in."},"description":"Business rule on action Go to sign in."}],"invariants":[],"effects":[{"type":"emit_event","event":"ui.signin.opened","id":"d30adf06","description":"Effect emit_event on action Go to sign in."},{"type":"transition_surface","target":"493e1030","id":"2fc3f2c1","description":"Effect transition_surface on action Go to sign in."}],"emittedEvents":["ui.signin.opened"],"transitions":[],"scenarios":[{"id":"4bf97a05","name":"Navigate to sign in","description":"Pure navigation; no gates.","stateOverrides":[],"parameterOverrides":[],"expectedStatus":"success","expectedTransition":"493e1030"}]},{"id":"ee9e011e","name":"Send welcome email","intent":"Cascade side-effect: send the welcome email after a successful signup.","parameters":[],"requiredStates":[],"rules":[{"id":"6fdcb82b","category":"validation","condition":{"left":"user.exists","operator":"is_false"},"effect":{"type":"block_action","reason":"Cannot send welcome email to a nonexistent user.","id":"dc14bf7b","description":"Effect block_action from a validation rule on action Send welcome email."},"description":"Cascade handler runs only after a real signup. Defensive: don't send welcome to a nonexistent user."}],"invariants":[],"effects":[{"type":"emit_event","event":"account.welcome_email.sent","id":"14f94d65","description":"Effect emit_event on action Send welcome email."}],"emittedEvents":["account.welcome_email.sent"],"transitions":[],"triggeredByEvent":"account.signup.completed"}],"rules":[{"id":"21bb6bee","category":"permissions","condition":{"left":"user.authenticated","operator":"is_true"},"effect":{"type":"block_action","reason":"You are already signed in.","id":"e287654b","description":"Effect block_action from a surface-level permissions rule on Sign up."},"description":"Cross-cutting: an already-signed-in visitor should not be on the sign-up surface."}],"invariants":[{"id":"a7cd0f5e","name":"Sign-up form initialized","condition":{"left":"form.emailValid","operator":"exists"},"message":"Sign-up form fields must be initialized before any action runs.","description":"Surface invariant on Sign up: Sign-up form initialized."}],"transitions":[{"id":"02a2ffbd","target":"493e1030","label":"Already have an account? Sign in","description":"Header link from sign up to sign in."},{"id":"bfe9c28f","target":"ab24f04f","label":"After signup","description":"On successful create, hand off to email verification."}]},{"id":"493e1030","name":"Sign in","type":"screen","description":"Authentication. Tracks failed attempts and locks the account after the fifth failure.","stateDefinitions":[{"id":"484d1248","path":"auth.credentialsValid","type":"boolean","defaultValue":true,"description":"Whether the email/password combination matches the user record."},{"id":"cd58dcba","path":"auth.failedAttempts","type":"number","defaultValue":0,"description":"Running count of consecutive failed sign-in attempts."},{"id":"7a116f8c","path":"auth.locked","type":"boolean","defaultValue":false,"description":"True once the account is locked after repeated failures."}],"actions":[{"id":"810b4632","name":"Sign in","intent":"Authenticate a returning customer.","parameters":[{"id":"eb2366ce","name":"email","type":"string","required":true,"description":"String parameter email for action Sign in."},{"id":"5509f24b","name":"password","type":"string","required":true,"description":"String parameter password for action Sign in."}],"requiredStates":["auth.credentialsValid","auth.failedAttempts","auth.locked"],"rules":[{"id":"0ca01598","category":"security","condition":{"kind":"any","conditions":[{"left":"auth.locked","operator":"is_true"},{"left":"auth.failedAttempts","operator":"greater_than","right":4}]},"effect":{"type":"block_action","reason":"Account locked. Reset your password to continue.","id":"07274118","description":"Effect block_action from a security rule on action Sign in."},"description":"Composite ANY: locked OR over the attempt ceiling = locked out."},{"id":"9e0bf599","category":"validation","condition":{"left":"auth.credentialsValid","operator":"is_false"},"effect":{"type":"block_action","reason":"Email or password is incorrect.","id":"603b8255","description":"Effect block_action from a validation rule on action Sign in."},"description":"Wrong credentials: bump the failed-attempt counter via Expression arithmetic."},{"id":"160dc1e9","category":"business","condition":{"left":"user.authenticated","operator":"is_true"},"effect":{"type":"block_action","reason":"You are already signed in.","id":"16a28782","description":"Effect block_action from a business rule on action Sign in."},"description":"Business rule on action Sign in."}],"invariants":[],"effects":[{"type":"set_state","path":"user.authenticated","value":true,"id":"60944aa3","description":"Effect set_state on action Sign in."},{"type":"set_state","path":"auth.failedAttempts","value":0,"id":"2726ac14","description":"Effect set_state on action Sign in."},{"type":"emit_event","event":"account.signin.succeeded","id":"ee0420b3","description":"Effect emit_event on action Sign in."}],"emittedEvents":["account.signin.succeeded","account.signin.failed"],"transitions":[],"scenarios":[{"id":"a4b369a9","name":"Happy path: valid credentials","stateOverrides":[{"path":"auth.credentialsValid","value":true},{"path":"auth.locked","value":false},{"path":"auth.failedAttempts","value":0},{"path":"user.authenticated","value":false},{"path":"user.exists","value":true}],"parameterOverrides":[{"parameterName":"email","value":"ada@example.com"},{"parameterName":"password","value":"StrongPass1!"}],"expectedStatus":"success","expectedAssertions":[{"path":"user.authenticated","operator":"is_true","description":"Asserts user.authenticated is_true."}],"description":"Scenario Happy path: valid credentials on action Sign in."},{"id":"c4fb666d","name":"Blocked: account locked","description":"Composite ANY rule trips when the user is locked or over the attempt ceiling.","stateOverrides":[{"path":"auth.failedAttempts","value":5},{"path":"auth.locked","value":true}],"parameterOverrides":[{"parameterName":"email","value":"ada@example.com"},{"parameterName":"password","value":"wrong"}],"expectedStatus":"blocked"},{"id":"5ff9d6a8","name":"Blocked: invalid credentials","stateOverrides":[{"path":"auth.credentialsValid","value":false},{"path":"auth.locked","value":false},{"path":"auth.failedAttempts","value":1}],"parameterOverrides":[{"parameterName":"email","value":"ada@example.com"},{"parameterName":"password","value":"wrong"}],"expectedStatus":"blocked","description":"Scenario Blocked: invalid credentials on action Sign in."}]},{"id":"de61b2f3","name":"Go to sign up","intent":"Navigate to sign up for a new visitor.","parameters":[],"requiredStates":[],"rules":[{"id":"f7342898","category":"business","condition":{"left":"user.authenticated","operator":"is_true"},"effect":{"type":"block_action","reason":"You are already signed in.","id":"b81dedf1","description":"Effect block_action from a business rule on action Go to sign up."},"description":"Business rule on action Go to sign up."}],"invariants":[],"effects":[{"type":"emit_event","event":"ui.signup.opened","id":"a04ec02f","description":"Effect emit_event on action Go to sign up."},{"type":"transition_surface","target":"86fcdef9","id":"a210a2da","description":"Effect transition_surface on action Go to sign up."}],"emittedEvents":["ui.signup.opened"],"transitions":[],"scenarios":[{"id":"3720bc70","name":"Navigate to sign up","stateOverrides":[],"parameterOverrides":[],"expectedStatus":"success","expectedTransition":"86fcdef9","description":"Scenario Navigate to sign up on action Go to sign up."}]}],"rules":[{"id":"790ca684","category":"permissions","condition":{"left":"user.authenticated","operator":"is_true"},"effect":{"type":"block_action","reason":"You are already signed in.","id":"3a326348","description":"Effect block_action from a surface-level permissions rule on Sign in."},"description":"Surface-level permissions rule on Sign in."}],"invariants":[{"id":"5c73680d","name":"Failed-attempt counter bounded","condition":{"left":"auth.failedAttempts","operator":"lower_than","right":100},"message":"Failed-attempt counter exceeded a sane upper bound; counter likely corrupted.","description":"Surface invariant on Sign in: Failed-attempt counter bounded."}],"transitions":[{"id":"fcb9ecd7","target":"86fcdef9","label":"Create an account","description":"Link from sign in back to sign up."}]},{"id":"ab24f04f","name":"Email verification","type":"screen","description":"Six-digit code verification. Caps retries to defend against brute-force.","stateDefinitions":[{"id":"a6eddc32","path":"verification.codeMatches","type":"boolean","defaultValue":true,"description":"Whether the submitted verification code matches the one on file."},{"id":"a4feec92","path":"verification.attempts","type":"number","defaultValue":0,"description":"Number of verification attempts so far."}],"actions":[{"id":"fb476114","name":"Verify email","intent":"Confirm the buyer's email with the code that was emailed.","parameters":[{"id":"3f8a4f5c","name":"code","type":"string","required":true,"description":"The six-digit verification code."}],"requiredStates":["user.authenticated","user.emailVerified","verification.codeMatches","verification.attempts"],"rules":[{"id":"006225f7","category":"business","condition":{"left":"user.emailVerified","operator":"is_true"},"effect":{"type":"block_action","reason":"Your email is already verified.","id":"aa328e4f","description":"Effect block_action from a business rule on action Verify email."},"description":"Business rule on action Verify email."},{"id":"8200c996","category":"security","condition":{"left":"verification.attempts","operator":"greater_than","right":4},"effect":{"type":"block_action","reason":"Too many attempts. Request a new code.","id":"322b1acd","description":"Effect block_action from a security rule on action Verify email."},"description":"Verification cap; brute-force defense."},{"id":"95f19dc5","category":"validation","condition":{"left":"verification.codeMatches","operator":"is_false"},"effect":{"type":"block_action","reason":"Invalid verification code.","id":"373ce15c","description":"Effect block_action from a validation rule on action Verify email."},"description":"Wrong code: bump the counter via Expression add and block."}],"invariants":[],"effects":[{"type":"set_state","path":"user.emailVerified","value":true,"id":"502e0267","description":"Effect set_state on action Verify email."},{"type":"emit_event","event":"account.email.verified","id":"fed1dd63","description":"Effect emit_event on action Verify email."}],"emittedEvents":["account.email.verified"],"transitions":[],"scenarios":[{"id":"8b8c386b","name":"Happy path: code matches","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"user.exists","value":true},{"path":"verification.codeMatches","value":true},{"path":"verification.attempts","value":0},{"path":"user.emailVerified","value":false}],"parameterOverrides":[{"parameterName":"code","value":"123456"}],"expectedStatus":"success","expectedAssertions":[{"path":"user.emailVerified","operator":"is_true","description":"Asserts user.emailVerified is_true."}],"description":"Scenario Happy path: code matches on action Verify email."},{"id":"1a98d863","name":"Blocked: attempt cap reached","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"verification.attempts","value":5}],"parameterOverrides":[{"parameterName":"code","value":"000000"}],"expectedStatus":"blocked","description":"Scenario Blocked: attempt cap reached on action Verify email."}]},{"id":"fc906d3b","name":"Resend verification code","intent":"Regenerate and re-send the verification code; bumps the counter using Expression arithmetic.","parameters":[],"requiredStates":["verification.attempts"],"rules":[{"id":"22562373","category":"security","condition":{"left":"verification.attempts","operator":"greater_than","right":4},"effect":{"type":"block_action","reason":"Too many resend requests. Try again later.","id":"1a3a7507","description":"Effect block_action from a security rule on action Resend verification code."},"description":"Security rule on action Resend verification code."}],"invariants":[],"effects":[{"type":"set_state","path":"verification.attempts","value":{"kind":"add","left":{"kind":"state","path":"verification.attempts"},"right":{"kind":"literal","value":1}},"id":"b1064561","description":"Effect set_state on action Resend verification code."},{"type":"emit_event","event":"account.email.verification_sent","id":"699c56b7","description":"Effect emit_event on action Resend verification code."}],"emittedEvents":["account.email.verification_sent"],"transitions":[],"scenarios":[{"id":"46251091","name":"Happy path: resend bumps counter","description":"Expression add: verification.attempts <- attempts + 1.","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"user.exists","value":true},{"path":"verification.attempts","value":2}],"parameterOverrides":[],"expectedStatus":"success","expectedAssertions":[{"path":"verification.attempts","operator":"equals","value":3,"description":"Asserts verification.attempts equals 3."}]}]}],"rules":[{"id":"9fd50090","category":"permissions","condition":{"left":"user.authenticated","operator":"is_false"},"effect":{"type":"block_action","reason":"Sign in to verify your email.","id":"45bf17a5","description":"Effect block_action from a surface-level permissions rule on Email verification."},"description":"Surface-level permissions rule on Email verification."}],"invariants":[{"id":"ca8fab52","name":"Verification counter bounded","condition":{"left":"verification.attempts","operator":"lower_than","right":100},"message":"Verification-attempt counter exceeded a sane upper bound.","description":"Surface invariant on Email verification: Verification counter bounded."}],"transitions":[{"id":"5c00c58d","target":"86fcdef9","label":"Cancel and start over","description":"Back to the sign-up surface."}]}],"personas":[{"id":"0ec7aa6b","name":"First-time visitor","description":"Brand-new visitor; not signed in, no record on file.","stateOverrides":[{"path":"user.authenticated","value":false},{"path":"user.exists","value":false},{"path":"user.emailVerified","value":false}],"parameterOverrides":[]},{"id":"f5a109a8","name":"Returning customer","description":"Account exists, email verified, valid credentials on file.","stateOverrides":[{"path":"user.exists","value":true},{"path":"user.emailVerified","value":true},{"path":"auth.credentialsValid","value":true}],"parameterOverrides":[]},{"id":"4730bb34","name":"Locked-out user","description":"Hit the failed-attempts ceiling.","stateOverrides":[{"path":"auth.failedAttempts","value":5},{"path":"auth.locked","value":true}],"parameterOverrides":[]}],"resources":[{"id":"bf5aef00","name":"Users database","description":"Primary store for user accounts and authentication state.","kind":"database","provider":"PostgreSQL","scope":"shared","sensitivity":"high","containsPii":true,"complianceTags":[],"accessMode":"read_write"},{"id":"ced6cb3e","name":"Transactional email service","description":"Sends verification codes and welcome emails.","kind":"http_api","provider":"Postmark","scope":"external","sensitivity":"medium","containsPii":true,"complianceTags":[],"accessMode":"write_only"}],"entities":[{"id":"8f6b148c","namespace":"user","description":"Authenticated customer record. PII; mirrored from the Users database.","fields":[{"id":"99b3783f","name":"authenticated","type":"boolean","path":"user.authenticated","description":"Boolean field authenticated on entity user."},{"id":"1b756e10","name":"exists","type":"boolean","path":"user.exists","description":"Boolean field exists on entity user."},{"id":"0e57fb6b","name":"emailVerified","type":"boolean","path":"user.emailVerified","description":"Boolean field emailVerified on entity user."}],"resourceId":"bf5aef00"}],"events":[{"id":"a38afde5","name":"account.signup.requested","description":"Signup form submitted with validation passing."},{"id":"021215a4","name":"account.signup.completed","description":"Account created and the visitor is now signed in.","payloadSchema":[{"name":"userId","type":"string","required":true,"description":"String payload field userId on event account.signup.completed."},{"name":"email","type":"string","required":true,"description":"String payload field email on event account.signup.completed."}]},{"id":"95e958c1","name":"account.signup.blocked","description":"Signup attempt blocked.","payloadSchema":[{"name":"reason","type":"string","required":true,"description":"String payload field reason on event account.signup.blocked."}]},{"id":"39710215","name":"account.email.verified","description":"Email verification succeeded.","payloadSchema":[{"name":"userId","type":"string","required":true,"description":"String payload field userId on event account.email.verified."}]},{"id":"e43b97b9","name":"account.email.verification_sent","description":"Verification code generated and dispatched.","payloadSchema":[{"name":"userId","type":"string","required":true,"description":"String payload field userId on event account.email.verification_sent."}]},{"id":"971b5ef9","name":"account.signin.succeeded","description":"Successful sign-in.","payloadSchema":[{"name":"userId","type":"string","required":true,"description":"String payload field userId on event account.signin.succeeded."}]},{"id":"2dddaca8","name":"account.signin.failed","description":"Failed sign-in attempt.","payloadSchema":[{"name":"reason","type":"string","required":true,"description":"String payload field reason on event account.signin.failed."}]},{"id":"017ee9e4","name":"account.welcome_email.sent","description":"Welcome email dispatched by the cascade handler.","payloadSchema":[{"name":"userId","type":"string","required":true,"description":"String payload field userId on event account.welcome_email.sent."}]},{"id":"3cf5f42f","name":"ui.signin.opened","description":"Buyer navigated to the Sign in surface."},{"id":"61e75089","name":"ui.signup.opened","description":"Buyer navigated to the Sign up surface."}],"createdAt":"2026-05-18T23:53:41.104Z","updatedAt":"2026-05-19T00:14:43.583Z","featureInvariants":[{"id":"8ca8e119","name":"Authenticated implies known user","condition":{"kind":"any","conditions":[{"left":"user.authenticated","operator":"is_false"},{"left":"user.exists","operator":"is_true"}]},"message":"Authenticated sessions must correspond to an existing user record.","description":"A session can only be authenticated for an account that exists. Composite implication: not(authenticated) OR exists."},{"id":"95d5d961","name":"Failed attempts never negative","condition":{"left":"auth.failedAttempts","operator":"greater_than","right":-1},"message":"Failed-attempts counter must stay non-negative."},{"id":"8b3696a7","name":"Verification attempts never negative","condition":{"left":"verification.attempts","operator":"greater_than","right":-1},"message":"Verification-attempts counter must stay non-negative."}],"expectedActions":["Create account","Sign in","Verify email","Resend verification code"],"nonGoals":["Password reset / forgot-password flow","Social login (OAuth, SAML)","Multi-factor authentication","Session management and refresh tokens","User profile editing"]}`),le={format:se,version:1,feature:ce},ue=e({default:()=>pe,feature:()=>fe,format:()=>de,version:()=>1}),de=`unspaghettit`,fe=JSON.parse(`{"id":"33fbb26d","name":"Cart & checkout","description":"Cart updates, coupons, addresses, payment selection, and place order. Refuses empty carts and unverified accounts, prices the order from line items, and lets admins override the subtotal.","surfaces":[{"id":"8412e977","name":"Cart","type":"screen","description":"Cart line items, totals, coupon application.","stateDefinitions":[{"id":"aaf0b580","path":"cart.lineItems","type":"array","defaultValue":[{"productId":"wireless-headphones","qty":1,"amount":2999},{"productId":"phone-case","qty":1,"amount":1499}],"description":"Cart line items: each {productId, qty, amount in cents}.","sharedWith":["2102bf6e"]},{"id":"227fd9a4","path":"cart.subtotal","type":"number","defaultValue":4498,"description":"Cents. Recomputed via sum_pluck on cart.lineItems[amount].","sharedWith":["2102bf6e"]},{"id":"994eb5cf","path":"cart.shipping","type":"number","defaultValue":599,"description":"Shipping cost in cents.","sharedWith":["2102bf6e"]},{"id":"56730178","path":"coupon.code","type":"string","defaultValue":"","description":"Most recently entered coupon code."},{"id":"bbe228d1","path":"coupon.applied","type":"boolean","defaultValue":false,"description":"Whether a coupon is currently applied."},{"id":"75344680","path":"coupon.discount","type":"number","defaultValue":0,"description":"Discount in cents."},{"id":"87b28027","path":"user.authenticated","type":"boolean","defaultValue":false,"description":"Sign-in status (mirrored).","sharedWith":["864ef35d","2102bf6e"]},{"id":"e044d003","path":"user.emailVerified","type":"boolean","defaultValue":false,"description":"Email verification status.","sharedWith":["2102bf6e"]}],"actions":[{"id":"8d775a6f","name":"Update quantity","intent":"Change the quantity of a cart line item.","parameters":[{"id":"dde3fd4a","name":"lineIndex","type":"number","required":true,"description":"Number parameter lineIndex for action Update quantity."},{"id":"d99ffbce","name":"newQuantity","type":"number","required":true,"description":"Number parameter newQuantity for action Update quantity."}],"requiredStates":[],"rules":[{"id":"e91bd3df","category":"validation","condition":{"left":"cart.subtotal","operator":"lower_than","right":0},"effect":{"type":"block_action","reason":"Cart in an invalid state.","id":"12ef7300","description":"Effect block_action from a validation rule on action Update quantity."},"description":"Validation rule on action Update quantity."}],"invariants":[],"effects":[{"type":"emit_event","event":"cart.quantity.updated","id":"408bd4ce","description":"Effect emit_event on action Update quantity."}],"emittedEvents":["cart.quantity.updated"],"transitions":[],"scenarios":[{"id":"f888a546","name":"Happy path: update quantity","stateOverrides":[],"parameterOverrides":[{"parameterName":"lineIndex","value":0},{"parameterName":"newQuantity","value":3}],"expectedStatus":"success","description":"Scenario Happy path: update quantity on action Update quantity."}]},{"id":"bdccba9e","name":"Remove item","intent":"Remove a line item from the cart.","parameters":[{"id":"871f9b58","name":"lineIndex","type":"number","required":true,"description":"Number parameter lineIndex for action Remove item."}],"requiredStates":[],"rules":[{"id":"1fee7533","category":"validation","condition":{"left":"cart.subtotal","operator":"equals","right":0},"effect":{"type":"block_action","reason":"Cart is empty; nothing to remove.","id":"68a4cf29","description":"Effect block_action from a validation rule on action Remove item."},"description":"Validation rule on action Remove item."}],"invariants":[],"effects":[{"type":"emit_event","event":"cart.item.removed","id":"6b4660cd","description":"Effect emit_event on action Remove item."}],"emittedEvents":["cart.item.removed"],"transitions":[],"scenarios":[{"id":"8c397a00","name":"Happy path: remove item","stateOverrides":[{"path":"cart.subtotal","value":4498}],"parameterOverrides":[{"parameterName":"lineIndex","value":0}],"expectedStatus":"success","description":"Scenario Happy path: remove item on action Remove item."}]},{"id":"cd9130fa","name":"Apply coupon","intent":"Redeem a coupon code against the cart.","parameters":[{"id":"3c6923bf","name":"code","type":"string","required":true,"bindToStatePath":"coupon.code","description":"String parameter code for action Apply coupon."}],"requiredStates":[],"rules":[{"id":"35481398","category":"business","condition":{"left":"coupon.applied","operator":"is_true"},"effect":{"type":"block_action","reason":"A coupon is already applied.","id":"b1052107","description":"Effect block_action from a business rule on action Apply coupon."},"description":"Business rule on action Apply coupon."},{"id":"8b5d3925","category":"validation","condition":{"left":"coupon.code","operator":"equals","right":"EXPIRED2025"},"effect":{"type":"block_action","reason":"Coupon expired.","id":"daa0e5ce","description":"Effect block_action from a validation rule on action Apply coupon."},"description":"Reject expired codes."},{"id":"07798ac9","category":"permissions","condition":{"left":"user.authenticated","operator":"is_false"},"effect":{"type":"block_action","reason":"Sign in to apply coupons.","id":"093f70b3","description":"Effect block_action from a permissions rule on action Apply coupon."},"description":"Permissions rule on action Apply coupon."}],"invariants":[],"effects":[{"type":"set_state","path":"coupon.applied","value":true,"id":"5266266c","description":"Effect set_state on action Apply coupon."},{"type":"set_state","path":"coupon.discount","value":500,"id":"631226ec","description":"Effect set_state on action Apply coupon."},{"type":"set_state","path":"cart.subtotal","value":{"kind":"sub","left":{"kind":"state","path":"cart.subtotal"},"right":{"kind":"literal","value":500}},"id":"5987b360","description":"Effect set_state on action Apply coupon."},{"type":"emit_event","event":"cart.coupon.applied","id":"3d6d11b8","description":"Effect emit_event on action Apply coupon."}],"emittedEvents":["cart.coupon.applied"],"transitions":[],"scenarios":[{"id":"d1fffba7","name":"Happy path: apply valid coupon (sub Expression discounts subtotal)","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"coupon.applied","value":false},{"path":"cart.subtotal","value":4498}],"parameterOverrides":[{"parameterName":"code","value":"SAVE10"}],"expectedStatus":"success","expectedAssertions":[{"path":"coupon.applied","operator":"is_true","description":"Asserts coupon.applied is_true."},{"path":"cart.subtotal","operator":"equals","value":3998,"description":"Asserts cart.subtotal equals 3998."}],"description":"Scenario Happy path: apply valid coupon (sub Expression discounts subtotal) on action Apply coupon."}]},{"id":"524a7937","name":"Recalculate subtotal","intent":"Recompute the cart subtotal from line items using a sum_pluck Expression.","parameters":[],"requiredStates":[],"rules":[{"id":"e9a53085","category":"validation","condition":{"left":"cart.subtotal","operator":"lower_than","right":0},"effect":{"type":"block_action","reason":"Cart state is invalid.","id":"ae4c77e4","description":"Effect block_action from a validation rule on action Recalculate subtotal."},"description":"Don't recompute when the cart is empty."},{"id":"90cccab0","category":"permissions","condition":{"left":"user.authenticated","operator":"is_false"},"effect":{"type":"block_action","reason":"Sign in to modify your cart.","id":"11b23665","description":"Effect block_action from a permissions rule on action Recalculate subtotal."},"description":"Permissions rule on action Recalculate subtotal."}],"invariants":[],"effects":[{"type":"set_state","path":"cart.subtotal","value":{"kind":"sum_pluck","operand":{"kind":"state","path":"cart.lineItems"},"field":"amount"},"id":"123eb629","description":"Effect set_state on action Recalculate subtotal."},{"type":"emit_event","event":"cart.subtotal.recomputed","id":"af4b13da","description":"Effect emit_event on action Recalculate subtotal."}],"emittedEvents":["cart.subtotal.recomputed"],"transitions":[],"scenarios":[{"id":"edc2e9e8","name":"sum_pluck: recompute subtotal from line items","description":"Two line items, 2999 + 1499 = 4498.","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"cart.lineItems","value":[{"productId":"wireless-headphones","qty":1,"amount":2999},{"productId":"phone-case","qty":1,"amount":1499}]}],"parameterOverrides":[],"expectedStatus":"success","expectedAssertions":[{"path":"cart.subtotal","operator":"equals","value":4498,"description":"Asserts cart.subtotal equals 4498."}]}]},{"id":"825ebb2e","name":"Proceed to checkout","intent":"Hand off to the checkout flow.","parameters":[],"requiredStates":[],"rules":[{"id":"49d89bf5","category":"business","condition":{"left":"cart.subtotal","operator":"equals","right":0},"effect":{"type":"block_action","reason":"Cart is empty.","id":"e6b8954c","description":"Effect block_action from a business rule on action Proceed to checkout."},"description":"Business rule on action Proceed to checkout."},{"id":"5a6d8cdd","category":"permissions","condition":{"kind":"any","conditions":[{"left":"user.authenticated","operator":"is_false"},{"left":"user.emailVerified","operator":"is_false"}]},"effect":{"type":"block_action","reason":"Sign in with a verified email to continue to checkout.","id":"5784edf8","description":"Effect block_action from a permissions rule on action Proceed to checkout."},"description":"Composite ANY: any unverified condition blocks checkout."}],"invariants":[],"effects":[{"type":"emit_event","event":"checkout.started","id":"1be6e7ed","description":"Effect emit_event on action Proceed to checkout."},{"type":"transition_surface","target":"2102bf6e","id":"28cfd4e5","description":"Effect transition_surface on action Proceed to checkout."}],"emittedEvents":["checkout.started","ui.checkout.opened"],"transitions":[],"scenarios":[{"id":"e64ef0bc","name":"Happy path: proceed to checkout","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"user.emailVerified","value":true},{"path":"cart.subtotal","value":4498}],"parameterOverrides":[],"expectedStatus":"success","expectedTransition":"2102bf6e","description":"Scenario Happy path: proceed to checkout on action Proceed to checkout."},{"id":"2e3aa293","name":"Blocked: composite ANY catches unverified buyer","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"user.emailVerified","value":false},{"path":"cart.subtotal","value":4498}],"parameterOverrides":[],"expectedStatus":"blocked","description":"Scenario Blocked: composite ANY catches unverified buyer on action Proceed to checkout."}]}],"rules":[{"id":"a41974e6","category":"ux_feedback","condition":{"kind":"all","conditions":[{"left":"cart.subtotal","operator":"greater_than","right":3499},{"left":"coupon.applied","operator":"is_false"}]},"effect":{"type":"show_message","message":"You qualify for free shipping! Apply a coupon for an extra discount.","id":"cc94cc62","description":"Effect show_message from a surface-level ux_feedback rule on Cart."},"description":"Free-shipping nudge: composite ALL fires for any cart action."}],"invariants":[{"id":"71de2e0f","name":"Cart subtotal non-negative","condition":{"left":"cart.subtotal","operator":"greater_than","right":-1},"message":"Cart subtotal went negative.","description":"Surface invariant on Cart: Cart subtotal non-negative."}],"transitions":[{"id":"f142df3b","target":"2102bf6e","label":"Checkout","description":"From the cart to the checkout flow."}]},{"id":"864ef35d","name":"Address book","type":"screen","description":"Manage shipping addresses; capped at 5 entries.","stateDefinitions":[{"id":"1ef22ea6","path":"addresses.count","type":"number","defaultValue":0,"description":"Saved shipping addresses count. Cap at 5.","sharedWith":["2102bf6e"]},{"id":"442ddcb7","path":"address.postalValid","type":"boolean","defaultValue":true,"description":"Whether the entered postal code passes the country check."}],"actions":[{"id":"2fd0ec30","name":"Add shipping address","intent":"Save a new shipping address.","parameters":[{"id":"92c7a84e","name":"fullName","type":"string","required":true,"description":"String parameter fullName for action Add shipping address."},{"id":"332ff42d","name":"street","type":"string","required":true,"description":"String parameter street for action Add shipping address."},{"id":"982b5098","name":"city","type":"string","required":true,"description":"String parameter city for action Add shipping address."},{"id":"f92b9a0e","name":"postalCode","type":"string","required":true,"description":"String parameter postalCode for action Add shipping address."}],"requiredStates":[],"rules":[{"id":"61efc813","category":"billing_quota","condition":{"left":"addresses.count","operator":"greater_than","right":4},"effect":{"type":"block_action","reason":"Maximum of 5 addresses reached. Remove one first.","id":"56fb74b8","description":"Effect block_action from a billing_quota rule on action Add shipping address."},"description":"Cap the address book at 5 entries."},{"id":"c4548f96","category":"validation","condition":{"left":"address.postalValid","operator":"is_false"},"effect":{"type":"block_action","reason":"Postal code is invalid for the selected country.","id":"ed73f7c6","description":"Effect block_action from a validation rule on action Add shipping address."},"description":"Validation rule on action Add shipping address."},{"id":"a5d2ff41","category":"permissions","condition":{"left":"user.authenticated","operator":"is_false"},"effect":{"type":"block_action","reason":"Sign in to add an address.","id":"959c6b46","description":"Effect block_action from a permissions rule on action Add shipping address."},"description":"Permissions rule on action Add shipping address."}],"invariants":[],"effects":[{"type":"set_state","path":"addresses.count","value":{"kind":"add","left":{"kind":"state","path":"addresses.count"},"right":{"kind":"literal","value":1}},"id":"997776a4","description":"Effect set_state on action Add shipping address."},{"type":"emit_event","event":"address.created","id":"6a19a96a","description":"Effect emit_event on action Add shipping address."}],"emittedEvents":["address.created"],"transitions":[],"scenarios":[{"id":"e10449e7","name":"Happy path: first address added","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"addresses.count","value":0},{"path":"address.postalValid","value":true}],"parameterOverrides":[{"parameterName":"fullName","value":"Ada Lovelace"},{"parameterName":"street","value":"1 Babbage Way"},{"parameterName":"city","value":"London"},{"parameterName":"postalCode","value":"WC1E 6BT"}],"expectedStatus":"success","expectedAssertions":[{"path":"addresses.count","operator":"equals","value":1,"description":"Asserts addresses.count equals 1."}],"description":"Scenario Happy path: first address added on action Add shipping address."},{"id":"0418d579","name":"Blocked: address book full","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"addresses.count","value":5},{"path":"address.postalValid","value":true}],"parameterOverrides":[{"parameterName":"fullName","value":"Ada Lovelace"},{"parameterName":"street","value":"1 Babbage Way"},{"parameterName":"city","value":"London"},{"parameterName":"postalCode","value":"WC1E 6BT"}],"expectedStatus":"blocked","description":"Scenario Blocked: address book full on action Add shipping address."}]},{"id":"21a6676c","name":"Continue to checkout","intent":"Return to checkout once an address is on file.","parameters":[],"requiredStates":[],"rules":[{"id":"3e9db1fa","category":"validation","condition":{"left":"addresses.count","operator":"equals","right":0},"effect":{"type":"block_action","reason":"Add an address before continuing.","id":"d890ff42","description":"Effect block_action from a validation rule on action Continue to checkout."},"description":"Validation rule on action Continue to checkout."}],"invariants":[],"effects":[{"type":"transition_surface","target":"2102bf6e","id":"c5d58fe1","description":"Effect transition_surface on action Continue to checkout."}],"emittedEvents":["ui.checkout.opened"],"transitions":[],"scenarios":[{"id":"cf547783","name":"Happy path: continue","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"addresses.count","value":1}],"parameterOverrides":[],"expectedStatus":"success","expectedTransition":"2102bf6e","description":"Scenario Happy path: continue on action Continue to checkout."}]}],"rules":[{"id":"dfccca82","category":"permissions","condition":{"left":"user.authenticated","operator":"is_false"},"effect":{"type":"block_action","reason":"Sign in to manage your address book.","id":"bc3dd8cf","description":"Effect block_action from a surface-level permissions rule on Address book."},"description":"Surface-level permissions rule on Address book."}],"invariants":[{"id":"5a73fdb1","name":"Address count bounded","condition":{"left":"addresses.count","operator":"lower_than","right":6},"message":"Address book exceeded its cap.","description":"Surface invariant on Address book: Address count bounded."}],"transitions":[{"id":"95b990e1","target":"2102bf6e","label":"Continue to checkout","description":"Transition from Address book to Continue to checkout."}]},{"id":"2102bf6e","name":"Checkout","type":"workflow","description":"Multi-step checkout: address → payment → review → place.","stateDefinitions":[{"id":"b51bc100","path":"checkout.step","type":"enum","defaultValue":"address","enumValues":["address","payment","review"],"description":"Current step in the checkout wizard."},{"id":"953ac567","path":"shipping.addressConfirmed","type":"boolean","defaultValue":false,"description":"Whether the buyer confirmed a shipping address."},{"id":"2c7ce796","path":"payment.method","type":"enum","defaultValue":"none","enumValues":["none","card","wallet"],"description":"Selected payment method."},{"id":"ce1709f3","path":"payment.cardValid","type":"boolean","defaultValue":true,"description":"Whether the card details pass validation."},{"id":"2ad3e033","path":"order.amount","type":"number","defaultValue":5097,"description":"Final order amount in cents. Computed via add Expression: subtotal + shipping - discount.","sharedWith":["8412e977"]},{"id":"3e485a8d","path":"fraud.flagged","type":"boolean","defaultValue":false,"description":"Whether the buyer was flagged by fraud detection."},{"id":"a2170d25","path":"admin.signedIn","type":"boolean","defaultValue":false,"description":"Whether a store admin is signed in (for override actions)."}],"actions":[{"id":"b5108b6e","name":"Confirm shipping address","intent":"Confirm the shipping address; advance to the payment step.","parameters":[{"id":"82b76492","name":"addressIndex","type":"number","required":true,"defaultValue":0,"description":"Number parameter addressIndex for action Confirm shipping address."}],"requiredStates":[],"rules":[{"id":"567f16d8","category":"business","condition":{"left":"checkout.step","operator":"not_equals","right":"address"},"effect":{"type":"block_action","reason":"Wrong step.","id":"72d952fc","description":"Effect block_action from a business rule on action Confirm shipping address."},"description":"Business rule on action Confirm shipping address."},{"id":"72f9a84a","category":"validation","condition":{"left":"addresses.count","operator":"equals","right":0},"effect":{"type":"block_action","reason":"Add an address before confirming.","id":"dcfbf283","description":"Effect block_action from a validation rule on action Confirm shipping address."},"description":"Validation rule on action Confirm shipping address."},{"id":"8d7ff7ec","category":"permissions","condition":{"left":"user.authenticated","operator":"is_false"},"effect":{"type":"block_action","reason":"Sign in to confirm a shipping address.","id":"0af9d7b3","description":"Effect block_action from a permissions rule on action Confirm shipping address."},"description":"Permissions rule on action Confirm shipping address."}],"invariants":[],"effects":[{"type":"set_state","path":"shipping.addressConfirmed","value":true,"id":"af421388","description":"Effect set_state on action Confirm shipping address."},{"type":"set_state","path":"checkout.step","value":"payment","id":"7bb5f14d","description":"Effect set_state on action Confirm shipping address."},{"type":"emit_event","event":"checkout.address.confirmed","id":"6552db5b","description":"Effect emit_event on action Confirm shipping address."}],"emittedEvents":["checkout.address.confirmed"],"transitions":[],"scenarios":[{"id":"c4891e30","name":"Happy path: confirm address","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"user.emailVerified","value":true},{"path":"checkout.step","value":"address"},{"path":"addresses.count","value":1}],"parameterOverrides":[{"parameterName":"addressIndex","value":0}],"expectedStatus":"success","expectedAssertions":[{"path":"checkout.step","operator":"equals","value":"payment","description":"Asserts checkout.step equals 'payment'."}],"description":"Scenario Happy path: confirm address on action Confirm shipping address."}]},{"id":"e177190c","name":"Select payment method","intent":"Pick a payment method and advance to review.","parameters":[{"id":"63e23674","name":"method","type":"enum","required":true,"enumValues":["card","wallet"],"bindToStatePath":"payment.method","description":"Enum parameter method for action Select payment method."}],"requiredStates":[],"rules":[{"id":"4d3207bb","category":"business","condition":{"left":"checkout.step","operator":"not_equals","right":"payment"},"effect":{"type":"block_action","reason":"Wrong step.","id":"cc589b0f","description":"Effect block_action from a business rule on action Select payment method."},"description":"Business rule on action Select payment method."},{"id":"2dba50cc","category":"validation","condition":{"left":"payment.cardValid","operator":"is_false"},"effect":{"type":"block_action","reason":"Card details look invalid.","id":"5bc88ad0","description":"Effect block_action from a validation rule on action Select payment method."},"description":"Validation rule on action Select payment method."},{"id":"dc31c9fe","category":"permissions","condition":{"left":"user.authenticated","operator":"is_false"},"effect":{"type":"block_action","reason":"Sign in to select a payment method.","id":"286bd399","description":"Effect block_action from a permissions rule on action Select payment method."},"description":"Permissions rule on action Select payment method."}],"invariants":[],"effects":[{"type":"set_state","path":"checkout.step","value":"review","id":"0e56006e","description":"Effect set_state on action Select payment method."},{"type":"emit_event","event":"checkout.payment.selected","id":"8e03b860","description":"Effect emit_event on action Select payment method."}],"emittedEvents":["checkout.payment.selected"],"transitions":[],"scenarios":[{"id":"70ffb8ae","name":"Happy path: pick card","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"user.emailVerified","value":true},{"path":"checkout.step","value":"payment"},{"path":"payment.cardValid","value":true}],"parameterOverrides":[{"parameterName":"method","value":"card"}],"expectedStatus":"success","description":"Scenario Happy path: pick card on action Select payment method."}]},{"id":"fd227eae","name":"Place order","intent":"Submit the order; computes order.amount via an add Expression and emits order.placed.","parameters":[],"requiredStates":[],"rules":[{"id":"e155ba56","category":"business","condition":{"left":"checkout.step","operator":"not_equals","right":"review"},"effect":{"type":"block_action","reason":"Complete the previous steps first.","id":"0b6a6d67","description":"Effect block_action from a business rule on action Place order."},"description":"Business rule on action Place order."},{"id":"bb30fdb4","category":"validation","condition":{"left":"payment.method","operator":"equals","right":"none"},"effect":{"type":"block_action","reason":"Pick a payment method.","id":"3f3fab2a","description":"Effect block_action from a validation rule on action Place order."},"description":"Validation rule on action Place order."},{"id":"6a580bb1","category":"security","condition":{"left":"fraud.flagged","operator":"is_true"},"effect":{"type":"block_action","reason":"Account flagged for fraud review.","id":"2efc4871","description":"Effect block_action from a security rule on action Place order."},"description":"Security rule on action Place order."}],"invariants":[],"effects":[{"type":"set_state","path":"order.amount","value":{"kind":"add","left":{"kind":"state","path":"cart.subtotal"},"right":{"kind":"state","path":"cart.shipping"}},"id":"863dd5df","description":"Effect set_state on action Place order."},{"type":"emit_event","event":"order.placed","id":"e271f733","description":"Effect emit_event on action Place order."},{"type":"show_message","message":"Order placed!","id":"52c4c22e","description":"Effect show_message on action Place order."}],"emittedEvents":["order.placed"],"transitions":[],"scenarios":[{"id":"fc64632a","name":"add Expression: order.amount = subtotal + shipping","description":"4498 + 599 = 5097.","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"user.emailVerified","value":true},{"path":"checkout.step","value":"review"},{"path":"payment.method","value":"card"},{"path":"fraud.flagged","value":false},{"path":"cart.subtotal","value":4498},{"path":"cart.shipping","value":599}],"parameterOverrides":[],"expectedStatus":"success","expectedAssertions":[{"path":"order.amount","operator":"equals","value":5097,"description":"Asserts order.amount equals 5097."}]}]},{"id":"047ed331","name":"Override subtotal (admin)","intent":"Admin-only manual subtotal adjustment. Bypasses the non-negative subtotal invariant so support staff can issue post-hoc adjustments.","parameters":[{"id":"03e23e7c","name":"newSubtotal","type":"number","required":true,"description":"New cart subtotal in cents (may be negative for credits)."}],"requiredStates":[],"rules":[{"id":"719cca37","category":"permissions","condition":{"left":"admin.signedIn","operator":"is_false"},"effect":{"type":"block_action","reason":"Admin sign-in required.","id":"a135b33b","description":"Effect block_action from a permissions rule on action Override subtotal (admin)."},"description":"Admin-only."},{"id":"aa323d6c","category":"audit","condition":{"left":"cart.subtotal","operator":"greater_than","right":1000000},"effect":{"type":"block_action","reason":"Subtotal beyond audit ceiling.","id":"cf727dfb","description":"Effect block_action from a audit rule on action Override subtotal (admin)."},"description":"Sanity ceiling. Even admins can't set subtotal beyond reason."},{"id":"07b4024f","category":"validation","condition":{"left":"cart.subtotal","operator":"lower_than","right":-100000},"effect":{"type":"block_action","reason":"Subtotal below the audit floor.","id":"33e0fa53","description":"Effect block_action from a validation rule on action Override subtotal (admin)."},"description":"Sanity floor: refuse adjustments below the audit floor (-100k cents = -$1k)."}],"invariants":[],"effects":[{"type":"set_state","path":"cart.subtotal","value":-100,"id":"81829fc0","description":"Effect set_state on action Override subtotal (admin)."},{"type":"emit_event","event":"audit.subtotal.override","id":"76908bf8","description":"Effect emit_event on action Override subtotal (admin)."}],"emittedEvents":["audit.subtotal.override"],"transitions":[],"scenarios":[{"id":"8d0a4317","name":"bypassInvariants: admin sets negative subtotal","description":"Subtotal goes to -100. The feature invariant \\"Cart subtotal non-negative\\" would normally block this; the action's bypassInvariants list lets it through.","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"user.emailVerified","value":true},{"path":"admin.signedIn","value":true},{"path":"cart.subtotal","value":4498}],"parameterOverrides":[{"parameterName":"newSubtotal","value":-100}],"expectedStatus":"success","expectedAssertions":[{"path":"cart.subtotal","operator":"equals","value":-100,"description":"Asserts cart.subtotal equals -100."}]}],"bypassInvariants":true}],"rules":[{"id":"512109db","category":"permissions","condition":{"kind":"any","conditions":[{"left":"user.authenticated","operator":"is_false"},{"left":"user.emailVerified","operator":"is_false"}]},"effect":{"type":"block_action","reason":"Sign in with a verified email to check out.","id":"1a807122","description":"Effect block_action from a surface-level permissions rule on Checkout."},"description":"Cross-cutting: sign-in required for the whole checkout."}],"invariants":[{"id":"50f2269a","name":"Order amount non-negative","condition":{"left":"order.amount","operator":"greater_than","right":-1},"message":"Order amount went negative.","description":"Surface invariant on Checkout: Order amount non-negative."}],"transitions":[{"id":"695c325f","target":"864ef35d","label":"Manage addresses","description":"Side trip to add or pick an address."}]}],"personas":[{"id":"a6bdf1b5","name":"Guest with full cart","description":"Two items, no coupon, not signed in.","stateOverrides":[{"path":"cart.lineItems","value":[{"productId":"wireless-headphones","qty":1,"amount":2999},{"productId":"phone-case","qty":1,"amount":1499}]},{"path":"cart.subtotal","value":4498}],"parameterOverrides":[]},{"id":"137e4026","name":"Verified buyer at checkout","description":"Signed in, email verified, address on file.","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"user.emailVerified","value":true},{"path":"addresses.count","value":1},{"path":"shipping.addressConfirmed","value":true}],"parameterOverrides":[]},{"id":"a3df9644","name":"Store admin","description":"Privileged operator who can manually adjust subtotals.","stateOverrides":[{"path":"admin.signedIn","value":true}],"parameterOverrides":[]}],"resources":[{"id":"5411c8d7","name":"Cart store","description":"Per-device cart state (anonymous + signed-in).","kind":"browser_storage","provider":"localStorage","scope":"device","sensitivity":"low","containsPii":false,"complianceTags":[],"accessMode":"read_write"},{"id":"6262d5ec","name":"Orders database","description":"Persisted orders. PII (shipping address, payment method).","kind":"database","provider":"PostgreSQL","scope":"shared","sensitivity":"high","containsPii":true,"complianceTags":[],"accessMode":"read_write"}],"entities":[{"id":"8786fba9","namespace":"cart","description":"Shopping cart state. Stored on-device.","fields":[{"id":"3901d6e8","name":"lineItems","type":"array","path":"cart.lineItems","description":"Array field lineItems on entity cart."},{"id":"c3f23562","name":"subtotal","type":"number","path":"cart.subtotal","description":"Number field subtotal on entity cart."}],"resourceId":"5411c8d7"},{"id":"a2da9cd2","namespace":"order","description":"Order summary.","fields":[{"id":"e0b1c395","name":"amount","type":"number","path":"order.amount","description":"Number field amount on entity order."}],"resourceId":"6262d5ec"}],"events":[{"id":"7256bcf8","name":"cart.item.added","description":"Item added to cart.","payloadSchema":[{"name":"productId","type":"string","required":true,"description":"String payload field productId on event cart.item.added."}]},{"id":"138c817e","name":"cart.item.removed","description":"Item removed from cart.","payloadSchema":[{"name":"lineIndex","type":"number","required":true,"description":"Number payload field lineIndex on event cart.item.removed."}]},{"id":"753192d6","name":"cart.quantity.updated","description":"Cart line quantity changed.","payloadSchema":[{"name":"lineIndex","type":"number","required":true,"description":"Number payload field lineIndex on event cart.quantity.updated."},{"name":"quantity","type":"number","required":true,"description":"Number payload field quantity on event cart.quantity.updated."}]},{"id":"ca058af6","name":"cart.subtotal.recomputed","description":"Cart subtotal recomputed from line items via sum_pluck."},{"id":"98f40a67","name":"cart.coupon.applied","description":"Coupon redeemed.","payloadSchema":[{"name":"code","type":"string","required":true,"description":"String payload field code on event cart.coupon.applied."}]},{"id":"500fea79","name":"address.created","description":"Shipping address saved.","payloadSchema":[{"name":"addressIndex","type":"number","required":true,"description":"Number payload field addressIndex on event address.created."}]},{"id":"2fef55d9","name":"checkout.started","description":"Buyer entered the checkout flow."},{"id":"dcc9b300","name":"checkout.address.confirmed","description":"Shipping address confirmed."},{"id":"ed856699","name":"checkout.payment.selected","description":"Payment method selected."},{"id":"94ed41de","name":"order.placed","description":"Order successfully placed.","payloadSchema":[{"name":"amount","type":"number","required":true,"description":"Number payload field amount on event order.placed."}]},{"id":"eb50d2ea","name":"audit.subtotal.override","description":"Admin overrode the cart subtotal; bypassed the subtotal invariant."},{"id":"7653d828","name":"ui.checkout.opened","description":"Buyer navigated to the Checkout surface."},{"id":"706dad5b","name":"ui.addresses.opened","description":"Buyer navigated to the Address book surface."}],"createdAt":"2026-05-18T23:53:41.168Z","updatedAt":"2026-05-19T00:18:56.499Z","featureInvariants":[{"id":"1e29895a","name":"Cart subtotal non-negative","condition":{"left":"cart.subtotal","operator":"greater_than","right":-1},"message":"Cart subtotal went negative across surfaces."},{"id":"8b1ce764","name":"Order amount non-negative","condition":{"left":"order.amount","operator":"greater_than","right":-1},"message":"Order amount went negative."},{"id":"b44e6a4e","name":"Address count never exceeds cap","condition":{"left":"addresses.count","operator":"lower_than","right":6},"message":"Address cap violated."}],"expectedActions":["Update quantity","Remove item","Apply coupon","Recalculate subtotal","Proceed to checkout","Add shipping address","Continue to checkout","Confirm shipping address","Select payment method","Place order","Override subtotal (admin)"],"nonGoals":["Tax calculation by region / VAT","Multi-currency pricing","Stored / saved payment methods","Gift cards, store credit, loyalty redemption","Real-time fraud-scoring integration (only fraud.flagged is modelled)","Carrier rate shopping and dynamic shipping options"]}`),pe={format:de,version:1,feature:fe},me=e({default:()=>_e,feature:()=>ge,format:()=>he,version:()=>1}),he=`unspaghettit`,ge=JSON.parse(`{"id":"6abcf051","name":"Catalog & reviews","description":"Search the catalog, open a product, post and filter reviews. Blocks profanity and link spam, gates age-restricted products to 18+ buyers, and assigns a review badge from the rating.","surfaces":[{"id":"0f9668ac","name":"Catalog","type":"screen","description":"Browse and search. Stock, prices, age flags surface here.","stateDefinitions":[{"id":"cf528800","path":"user.authenticated","type":"boolean","defaultValue":false,"description":"Whether the visitor is signed in (mirrored from Account feature).","sharedWith":["584d0e24","ed79f2cb"]},{"id":"6ae3339f","path":"user.age","type":"number","defaultValue":30,"description":"Visitor age. Drives the age-gate on restricted products.","sharedWith":["584d0e24"]},{"id":"5d4c1e4b","path":"search.query","type":"string","defaultValue":"","description":"Most recent search query."},{"id":"658868bd","path":"search.resultCount","type":"number","defaultValue":0,"description":"Number of results returned by the most recent search."},{"id":"6e42b820","path":"product.viewing","type":"string","defaultValue":"","description":"ID of the currently focused product. Bound across surfaces.","sharedWith":["584d0e24","ed79f2cb"]},{"id":"e81afb4e","path":"product.stock","type":"number","defaultValue":12,"description":"Units in stock for the focused product."}],"actions":[{"id":"b8888760","name":"Search products","intent":"Run a catalog search and store the query + result count.","parameters":[{"id":"fd95766a","name":"query","type":"string","required":true,"description":"Search query text.","bindToStatePath":"search.query"}],"requiredStates":[],"rules":[{"id":"1fee0314","category":"validation","condition":{"left":"search.query","operator":"equals","right":""},"effect":{"type":"block_action","reason":"Enter at least one character to search.","id":"0f5c1a81","description":"Effect block_action from a validation rule on action Search products."},"description":"Reject empty searches."}],"invariants":[],"effects":[{"type":"emit_event","event":"catalog.search.executed","id":"4fc1435e","description":"Effect emit_event on action Search products."}],"emittedEvents":["catalog.search.executed"],"transitions":[],"scenarios":[{"id":"9413f83b","name":"Happy path: search runs","stateOverrides":[],"parameterOverrides":[{"parameterName":"query","value":"wireless headphones"}],"expectedStatus":"success","expectedAssertions":[{"path":"search.query","operator":"equals","value":"wireless headphones","description":"Asserts search.query equals 'wireless headphones'."}],"description":"Scenario Happy path: search runs on action Search products."},{"id":"01e19621","name":"Blocked: empty query","stateOverrides":[],"parameterOverrides":[{"parameterName":"query","value":""}],"expectedStatus":"blocked","description":"Scenario Blocked: empty query on action Search products."}]},{"id":"8cba27eb","name":"View product","intent":"Open the product detail page for a focused product.","parameters":[{"id":"862fcdb4","name":"productId","type":"string","required":true,"description":"Identifier of the product to focus.","bindToStatePath":"product.viewing"}],"requiredStates":[],"rules":[{"id":"6fb8638f","category":"validation","condition":{"left":"product.viewing","operator":"equals","right":""},"effect":{"type":"block_action","reason":"Pick a product to view.","id":"d989cd88","description":"Effect block_action from a validation rule on action View product."},"description":"Validation rule on action View product."}],"invariants":[],"effects":[{"type":"emit_event","event":"catalog.product.viewed","id":"ffcac9aa","description":"Effect emit_event on action View product."},{"type":"transition_surface","target":"584d0e24","id":"a544c757","description":"Effect transition_surface on action View product."}],"emittedEvents":["ui.product.opened","catalog.product.viewed"],"transitions":[],"scenarios":[{"id":"8ebaba9f","name":"Happy path: open product","stateOverrides":[],"parameterOverrides":[{"parameterName":"productId","value":"wireless-headphones"}],"expectedStatus":"success","expectedTransition":"584d0e24","description":"Scenario Happy path: open product on action View product."}]}],"rules":[{"id":"34197871","category":"ux_feedback","condition":{"left":"product.stock","operator":"lower_than","right":0},"effect":{"type":"block_action","reason":"Stock data is missing.","id":"225068f9","description":"Effect block_action from a surface-level ux_feedback rule on Catalog."},"description":"Light gate so the panel shows visible gating."}],"invariants":[{"id":"8e9d4754","name":"Stock counter non-negative","condition":{"left":"product.stock","operator":"greater_than","right":-1},"message":"Stock counter went negative; inventory feed is corrupted.","description":"Surface invariant on Catalog: Stock counter non-negative."}],"transitions":[{"id":"dffa9ad3","target":"584d0e24","label":"Open product","description":"Tap a search result to focus a product."}]},{"id":"584d0e24","name":"Product details","type":"screen","description":"Single product. Reviews, recommendations, submit-review form.","stateDefinitions":[{"id":"93737004","path":"product.ageRestricted","type":"boolean","defaultValue":false,"description":"Whether the focused product requires an 18+ buyer."},{"id":"0ebce981","path":"product.reviews","type":"array","defaultValue":[],"description":"Reviews array (each: { rating, verified, helpful }). Used by sum_pluck and count_where Expressions.","sharedWith":["ed79f2cb"]},{"id":"17b904fa","path":"review.draft.rating","type":"number","defaultValue":5,"description":"Rating from the in-flight review draft (1-5)."},{"id":"581ef07d","path":"review.draft.text","type":"string","defaultValue":"","description":"Free-text body of the in-flight review draft."},{"id":"29340300","path":"review.draft.verifiedPurchase","type":"boolean","defaultValue":false,"description":"Whether the reviewer purchased the product."},{"id":"54579da7","path":"review.draft.profanityFlagged","type":"boolean","defaultValue":false,"description":"Whether the moderation pass flagged profanity."},{"id":"3e6fbfe5","path":"review.lastBadge","type":"string","defaultValue":"","description":"Badge label written by the switch Expression after a submit (\\"Positive\\" | \\"Mixed\\" | \\"Critical\\")."}],"actions":[{"id":"7b685cfb","name":"View product","intent":"Refocus the product page on a different product.","parameters":[{"id":"82e680c1","name":"productId","type":"string","required":true,"bindToStatePath":"product.viewing","description":"String parameter productId for action View product."}],"requiredStates":[],"rules":[{"id":"c5413b3c","category":"validation","condition":{"left":"product.viewing","operator":"equals","right":""},"effect":{"type":"block_action","reason":"Pick a product first.","id":"0394e153","description":"Effect block_action from a validation rule on action View product."},"description":"Validation rule on action View product."}],"invariants":[],"effects":[{"type":"emit_event","event":"catalog.product.viewed","id":"c455d58f","description":"Effect emit_event on action View product."}],"emittedEvents":["catalog.product.viewed"],"transitions":[],"scenarios":[{"id":"202e7dca","name":"Happy path: refocus product","stateOverrides":[],"parameterOverrides":[{"parameterName":"productId","value":"kindle-paperwhite"}],"expectedStatus":"success","description":"Scenario Happy path: refocus product on action View product."}]},{"id":"c2f761d2","name":"Submit review","intent":"Submit a new product review; sets review.lastBadge via a switch Expression based on rating.","parameters":[{"id":"19faee1f","name":"rating","type":"number","required":true,"bindToStatePath":"review.draft.rating","description":"Number parameter rating for action Submit review."},{"id":"2b419c21","name":"reviewText","type":"string","required":true,"bindToStatePath":"review.draft.text","description":"String parameter reviewText for action Submit review."}],"requiredStates":[],"rules":[{"id":"88b60113","category":"permissions","condition":{"left":"user.authenticated","operator":"is_false"},"effect":{"type":"block_action","reason":"Sign in to submit a review.","id":"dd348b1b","description":"Effect block_action from a permissions rule on action Submit review."},"description":"Permissions rule on action Submit review."},{"id":"2de96ce6","category":"compliance","condition":{"kind":"any","conditions":[{"left":"review.draft.profanityFlagged","operator":"is_true"},{"left":"review.draft.text","operator":"contains","right":"http"}]},"effect":{"type":"block_action","reason":"Review violates the content policy (profanity or external links).","id":"52c90123","description":"Effect block_action from a compliance rule on action Submit review."},"description":"Composite OR: profanity OR external link."},{"id":"d86b6a19","category":"validation","condition":{"left":"review.draft.rating","operator":"lower_than","right":1},"effect":{"type":"block_action","reason":"Rating must be at least 1.","id":"d1798897","description":"Effect block_action from a validation rule on action Submit review."},"description":"Validation rule on action Submit review."}],"invariants":[],"effects":[{"type":"set_state","path":"review.lastBadge","value":{"kind":"switch","cases":[{"when":{"left":"review.draft.rating","operator":"greater_than","right":3},"then":{"kind":"literal","value":"Positive"}},{"when":{"left":"review.draft.rating","operator":"greater_than","right":1},"then":{"kind":"literal","value":"Mixed"}}],"default":{"kind":"literal","value":"Critical"}},"id":"e61a7485","description":"Effect set_state on action Submit review."},{"type":"emit_event","event":"review.submitted","id":"546c5e8a","description":"Effect emit_event on action Submit review."},{"type":"show_message","message":"Thanks for your review!","id":"2ac282ef","description":"Effect show_message on action Submit review."}],"emittedEvents":["review.submitted"],"transitions":[],"scenarios":[{"id":"7105dc9f","name":"Happy path: 5-star → Positive badge","description":"Switch Expression picks the first case (rating > 3).","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"review.draft.profanityFlagged","value":false}],"parameterOverrides":[{"parameterName":"rating","value":5},{"parameterName":"reviewText","value":"Loved it."}],"expectedStatus":"success","expectedAssertions":[{"path":"review.lastBadge","operator":"equals","value":"Positive","description":"Asserts review.lastBadge equals 'Positive'."}]},{"id":"649c187b","name":"Switch: 2-star → Mixed badge","description":"Switch Expression falls to the second case (rating > 1 but not > 3).","stateOverrides":[{"path":"user.authenticated","value":true}],"parameterOverrides":[{"parameterName":"rating","value":2},{"parameterName":"reviewText","value":"It was OK."}],"expectedStatus":"success","expectedAssertions":[{"path":"review.lastBadge","operator":"equals","value":"Mixed","description":"Asserts review.lastBadge equals 'Mixed'."}]},{"id":"8840a966","name":"Switch: 1-star → Critical badge","description":"Switch Expression falls through to default.","stateOverrides":[{"path":"user.authenticated","value":true}],"parameterOverrides":[{"parameterName":"rating","value":1},{"parameterName":"reviewText","value":"Not great."}],"expectedStatus":"success","expectedAssertions":[{"path":"review.lastBadge","operator":"equals","value":"Critical","description":"Asserts review.lastBadge equals 'Critical'."}]}]},{"id":"9dc15c7e","name":"Click recommendation","intent":"Refocus the page on a recommended product.","parameters":[{"id":"d184dcd3","name":"recommendedProductId","type":"string","required":true,"bindToStatePath":"product.viewing","description":"String parameter recommendedProductId for action Click recommendation."}],"requiredStates":[],"rules":[{"id":"ce82b017","category":"validation","condition":{"left":"product.viewing","operator":"equals","right":""},"effect":{"type":"block_action","reason":"No product to refocus.","id":"c831dceb","description":"Effect block_action from a validation rule on action Click recommendation."},"description":"Validation rule on action Click recommendation."}],"invariants":[],"effects":[{"type":"emit_event","event":"recommendation.clicked","id":"9de80b1d","description":"Effect emit_event on action Click recommendation."}],"emittedEvents":["recommendation.clicked"],"transitions":[],"scenarios":[{"id":"efaf9525","name":"Happy path: click reco","stateOverrides":[],"parameterOverrides":[{"parameterName":"recommendedProductId","value":"phone-case"}],"expectedStatus":"success","description":"Scenario Happy path: click reco on action Click recommendation."}]},{"id":"1fecaf55","name":"Open all reviews","intent":"Navigate to the dedicated reviews page.","parameters":[],"requiredStates":[],"rules":[{"id":"3ce0bcab","category":"validation","condition":{"left":"product.viewing","operator":"equals","right":""},"effect":{"type":"block_action","reason":"Focus a product before opening its reviews.","id":"6a570826","description":"Effect block_action from a validation rule on action Open all reviews."},"description":"Validation rule on action Open all reviews."}],"invariants":[],"effects":[{"type":"emit_event","event":"ui.allreviews.opened","id":"a025767b","description":"Effect emit_event on action Open all reviews."},{"type":"transition_surface","target":"ed79f2cb","id":"7735a99a","description":"Effect transition_surface on action Open all reviews."}],"emittedEvents":["ui.allreviews.opened"],"transitions":[],"scenarios":[{"id":"5db2f15b","name":"Happy path: open all reviews","stateOverrides":[{"path":"product.viewing","value":"wireless-headphones"}],"parameterOverrides":[],"expectedStatus":"success","expectedTransition":"ed79f2cb","description":"Scenario Happy path: open all reviews on action Open all reviews."}]}],"rules":[{"id":"6c457370","category":"compliance","condition":{"kind":"all","conditions":[{"left":"user.age","operator":"lower_than","right":18},{"left":"product.ageRestricted","operator":"is_true"}]},"effect":{"type":"block_action","reason":"This product is restricted to buyers aged 18 or over.","id":"87bed071","description":"Effect block_action from a surface-level compliance rule on Product details."},"description":"Composite ALL: under-18 buyer AND age-restricted product = block. Demonstrates composite condition that fixes the legacy over-broad gate."}],"invariants":[{"id":"174c1555","name":"Review rating range","condition":{"left":"review.draft.rating","operator":"lower_than","right":6},"message":"Review rating must be 1–5.","description":"Surface invariant on Product details: Review rating range."}],"transitions":[{"id":"02bf7369","target":"ed79f2cb","label":"See all reviews","description":"From product details to the dedicated reviews list."},{"id":"dbd4cbfc","target":"0f9668ac","label":"Back to catalog","description":"Header link back to the catalog."}]},{"id":"ed79f2cb","name":"All reviews","type":"screen","description":"Dedicated reviews list with filter, sort, and helpful counter.","stateDefinitions":[{"id":"11c1d139","path":"reviews.filter.minRating","type":"number","defaultValue":1,"description":"Minimum rating filter."},{"id":"2cb0d263","path":"reviews.filter.verifiedOnly","type":"boolean","defaultValue":false,"description":"Whether to only show verified-purchase reviews."},{"id":"bbacecfd","path":"reviews.sortBy","type":"enum","defaultValue":"helpful","enumValues":["helpful","newest","highest","lowest"],"description":"Sort order for the reviews list."},{"id":"cc811899","path":"reviews.helpfulCount","type":"number","defaultValue":0,"description":"Number of reviews marked helpful so far. Recomputed via count_where Expression."}],"actions":[{"id":"72990024","name":"Filter reviews","intent":"Adjust the reviews list filter.","parameters":[{"id":"dd48b21c","name":"minRating","type":"number","required":true,"bindToStatePath":"reviews.filter.minRating","description":"Number parameter minRating for action Filter reviews."},{"id":"baa4a1c2","name":"verifiedOnly","type":"boolean","required":true,"defaultValue":false,"bindToStatePath":"reviews.filter.verifiedOnly","description":"Boolean parameter verifiedOnly for action Filter reviews."}],"requiredStates":[],"rules":[{"id":"1205fc2e","category":"validation","condition":{"left":"reviews.filter.minRating","operator":"greater_than","right":5},"effect":{"type":"block_action","reason":"Rating filter must be 1-5.","id":"0490f009","description":"Effect block_action from a validation rule on action Filter reviews."},"description":"Rating filter must be within 1–5."}],"invariants":[],"effects":[{"type":"emit_event","event":"reviews.filtered","id":"ef3132d5","description":"Effect emit_event on action Filter reviews."}],"emittedEvents":["reviews.filtered"],"transitions":[],"scenarios":[{"id":"02569a00","name":"Filter to verified 5-star","stateOverrides":[{"path":"product.viewing","value":"wireless-headphones"}],"parameterOverrides":[{"parameterName":"minRating","value":5},{"parameterName":"verifiedOnly","value":true}],"expectedStatus":"success","expectedAssertions":[{"path":"reviews.filter.minRating","operator":"equals","value":5,"description":"Asserts reviews.filter.minRating equals 5."}],"description":"Scenario Filter to verified 5-star on action Filter reviews."}]},{"id":"f2542d6b","name":"Sort reviews","intent":"Adjust the reviews list sort order.","parameters":[{"id":"1271ac52","name":"sortBy","type":"enum","required":true,"enumValues":["helpful","newest","highest","lowest"],"bindToStatePath":"reviews.sortBy","description":"Enum parameter sortBy for action Sort reviews."}],"requiredStates":[],"rules":[{"id":"15ef098f","category":"validation","condition":{"left":"reviews.sortBy","operator":"equals","right":""},"effect":{"type":"block_action","reason":"Pick a supported sort order.","id":"c1520400","description":"Effect block_action from a validation rule on action Sort reviews."},"description":"Sort key must be supported."}],"invariants":[],"effects":[{"type":"emit_event","event":"reviews.sorted","id":"6248f179","description":"Effect emit_event on action Sort reviews."}],"emittedEvents":["reviews.sorted"],"transitions":[],"scenarios":[{"id":"5c7d69ac","name":"Sort by newest","stateOverrides":[{"path":"product.viewing","value":"wireless-headphones"}],"parameterOverrides":[{"parameterName":"sortBy","value":"newest"}],"expectedStatus":"success","expectedAssertions":[{"path":"reviews.sortBy","operator":"equals","value":"newest","description":"Asserts reviews.sortBy equals 'newest'."}],"description":"Scenario Sort by newest on action Sort reviews."}]},{"id":"c201af29","name":"Mark review helpful","intent":"Mark a review helpful and recompute the helpful counter via count_where Expression.","parameters":[{"id":"8e1b084c","name":"reviewIndex","type":"number","required":true,"description":"Position of the review in the array."}],"requiredStates":[],"rules":[{"id":"4369bdbd","category":"permissions","condition":{"left":"user.authenticated","operator":"is_false"},"effect":{"type":"block_action","reason":"Sign in to mark reviews helpful.","id":"5a9027ae","description":"Effect block_action from a permissions rule on action Mark review helpful."},"description":"Permissions rule on action Mark review helpful."},{"id":"21e417e1","category":"validation","condition":{"left":"reviews.helpfulCount","operator":"lower_than","right":0},"effect":{"type":"block_action","reason":"Review index out of range.","id":"a5a76100","description":"Effect block_action from a validation rule on action Mark review helpful."},"description":"reviewIndex must point at a real entry."}],"invariants":[],"effects":[{"type":"set_state","path":"reviews.helpfulCount","value":{"kind":"count_where","operand":{"kind":"state","path":"product.reviews"},"field":"helpful","equals":{"kind":"literal","value":true}},"id":"5d46da1e","description":"Effect set_state on action Mark review helpful."},{"type":"emit_event","event":"review.helpful_clicked","id":"17132f7d","description":"Effect emit_event on action Mark review helpful."}],"emittedEvents":["review.helpful_clicked"],"transitions":[],"scenarios":[{"id":"f201d9c1","name":"count_where: recompute helpful counter","description":"Array has 3 entries with helpful=true. count_where Expression sets the counter to 3.","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"product.viewing","value":"wireless-headphones"},{"path":"product.reviews","value":[{"rating":5,"verified":true,"helpful":true},{"rating":4,"verified":true,"helpful":false},{"rating":5,"verified":false,"helpful":true},{"rating":3,"verified":true,"helpful":true}]}],"parameterOverrides":[{"parameterName":"reviewIndex","value":0}],"expectedStatus":"success","expectedAssertions":[{"path":"reviews.helpfulCount","operator":"equals","value":3,"description":"Asserts reviews.helpfulCount equals 3."}]}]}],"rules":[{"id":"3862c8e6","category":"permissions","condition":{"left":"product.viewing","operator":"equals","right":""},"effect":{"type":"block_action","reason":"Open a product first.","id":"d8589297","description":"Effect block_action from a surface-level permissions rule on All reviews."},"description":"Browsing reviews requires a focused product."}],"invariants":[{"id":"94f42470","name":"Helpful counter non-negative","condition":{"left":"reviews.helpfulCount","operator":"greater_than","right":-1},"message":"Helpful counter went negative.","description":"Surface invariant on All reviews: Helpful counter non-negative."}],"transitions":[{"id":"806b6d90","target":"584d0e24","label":"Back to product","description":"Return to the product detail page."}]}],"personas":[{"id":"783f1e9d","name":"Anonymous browser","description":"Not signed in. Can browse and search but not review.","stateOverrides":[{"path":"user.authenticated","value":false}],"parameterOverrides":[]},{"id":"5d051dee","name":"Signed-in reviewer","description":"Authenticated; verified purchase; may submit a review.","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"review.draft.verifiedPurchase","value":true}],"parameterOverrides":[]},{"id":"9623ae70","name":"Under-18 shopper","description":"Authenticated minor; age-restricted products are blocked.","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"user.age","value":16}],"parameterOverrides":[]}],"resources":[{"id":"1d5ddcd1","name":"Catalog database","description":"Product listings, stock, prices.","kind":"database","provider":"PostgreSQL","scope":"shared","sensitivity":"low","containsPii":false,"complianceTags":[],"accessMode":"read_only"},{"id":"27e74da1","name":"Reviews database","description":"User-submitted reviews. Reviewer identity is PII.","kind":"database","provider":"PostgreSQL","scope":"shared","sensitivity":"medium","containsPii":true,"complianceTags":[],"accessMode":"read_write"}],"entities":[{"id":"2c3d7620","namespace":"product","description":"Product catalog entry.","fields":[{"id":"557d045b","name":"viewing","type":"string","path":"product.viewing","description":"String field viewing on entity product."},{"id":"ff59001c","name":"stock","type":"number","path":"product.stock","description":"Number field stock on entity product."},{"id":"f033e0a9","name":"ageRestricted","type":"boolean","path":"product.ageRestricted","description":"Boolean field ageRestricted on entity product."}],"resourceId":"1d5ddcd1"},{"id":"a7fd9281","namespace":"review","description":"Product review with rating and helpful counter.","fields":[{"id":"ec6c18c0","name":"rating","type":"number","path":"review.draft.rating","description":"Number field rating on entity review."},{"id":"4ec7adbc","name":"text","type":"string","path":"review.draft.text","description":"String field text on entity review."},{"id":"065b2d57","name":"verifiedPurchase","type":"boolean","path":"review.draft.verifiedPurchase","description":"Boolean field verifiedPurchase on entity review."}],"resourceId":"27e74da1"}],"events":[{"id":"87125567","name":"catalog.search.executed","description":"Catalog search ran.","payloadSchema":[{"name":"query","type":"string","required":true,"description":"String payload field query on event catalog.search.executed."},{"name":"resultCount","type":"number","required":false,"description":"Number payload field resultCount on event catalog.search.executed."}]},{"id":"7846cf41","name":"catalog.product.viewed","description":"Product detail page opened.","payloadSchema":[{"name":"productId","type":"string","required":true,"description":"String payload field productId on event catalog.product.viewed."}]},{"id":"f3eafd64","name":"review.submitted","description":"A new product review was submitted.","payloadSchema":[{"name":"productId","type":"string","required":true,"description":"String payload field productId on event review.submitted."},{"name":"rating","type":"number","required":true,"description":"Number payload field rating on event review.submitted."}]},{"id":"6f742e8d","name":"review.helpful_clicked","description":"A review was marked helpful.","payloadSchema":[{"name":"reviewIndex","type":"number","required":true,"description":"Number payload field reviewIndex on event review.helpful_clicked."}]},{"id":"92724e5f","name":"recommendation.clicked","description":"\\"You may also like\\" recommendation clicked.","payloadSchema":[{"name":"productId","type":"string","required":true,"description":"String payload field productId on event recommendation.clicked."}]},{"id":"b60206d2","name":"reviews.filtered","description":"Reviews list filter changed.","payloadSchema":[{"name":"minRating","type":"number","required":true,"description":"Number payload field minRating on event reviews.filtered."},{"name":"verifiedOnly","type":"boolean","required":true,"description":"Boolean payload field verifiedOnly on event reviews.filtered."}]},{"id":"a7f25598","name":"reviews.sorted","description":"Reviews list sort order changed.","payloadSchema":[{"name":"sortBy","type":"string","required":true,"description":"String payload field sortBy on event reviews.sorted."}]},{"id":"bf8f9645","name":"ui.allreviews.opened","description":"Buyer opened the all-reviews page."},{"id":"71f0d82a","name":"ui.product.opened","description":"Buyer opened a product detail page."}],"createdAt":"2026-05-18T23:53:41.140Z","updatedAt":"2026-05-19T00:14:49.203Z","featureInvariants":[{"id":"283d71a1","name":"Helpful counter non-negative","condition":{"left":"reviews.helpfulCount","operator":"greater_than","right":-1},"message":"Helpful counter went negative across surfaces."},{"id":"72fb047b","name":"Product stock never negative","condition":{"left":"product.stock","operator":"greater_than","right":-1},"message":"Product stock went negative."}],"expectedActions":["Search products","View product","Submit review","Click recommendation","Open all reviews","Filter reviews","Sort reviews","Mark review helpful"],"nonGoals":["Personalised recommendations engine","Review moderation queue and reviewer reputation","Product comparison tables","Saved lists and wishlists","Catalog ingestion / vendor product onboarding"]}`),_e={format:he,version:1,feature:ge},ve=e({default:()=>xe,feature:()=>be,format:()=>ye,version:()=>1}),ye=`unspaghettit`,be=JSON.parse(`{"id":"5da6bb5c","name":"Order fulfillment","description":"Order lifecycle from preparing to delivered. Cancellation is allowed before shipping; signature is required on delivery confirmation. Automatically kicks off when an order is placed.","surfaces":[{"id":"da9b0526","name":"Order tracking & delivery","type":"screen","description":"Order status state machine. Preparing → shipped → out for delivery → delivered.","stateDefinitions":[{"id":"05ff3f48","path":"order.status","type":"enum","defaultValue":"preparing","enumValues":["preparing","shipped","out_for_delivery","delivered","cancelled"],"description":"Current order status. Drives the state machine."},{"id":"549cba7d","path":"order.id","type":"string","defaultValue":"ORD-1042","description":"Order identifier (server-issued)."},{"id":"b7a31432","path":"order.statusLabel","type":"string","defaultValue":"Preparing your order","description":"Human-readable label, derived from order.status via a switch Expression on every transition."},{"id":"041bb67f","path":"order.trackingInitialized","type":"boolean","defaultValue":false,"description":"Whether the tracking record was initialized by the cascade handler."},{"id":"e4993a5a","path":"delivery.signatureRequired","type":"boolean","defaultValue":true,"description":"Whether a signature is required for confirmation."},{"id":"8c3e844e","path":"delivery.signatureCollected","type":"boolean","defaultValue":false,"description":"Whether the buyer's signature was captured."},{"id":"d4bbd9a4","path":"user.authenticated","type":"boolean","defaultValue":false,"description":"Sign-in status (mirrored)."},{"id":"b5a76912","path":"carrier.signedIn","type":"boolean","defaultValue":false,"description":"Whether the carrier system is authenticated."}],"actions":[{"id":"712a0ef0","name":"Initialize tracking","intent":"Cascade handler: when an order is placed, create its tracking record and seed the status label.","parameters":[],"requiredStates":[],"rules":[{"id":"a4067017","category":"permissions","condition":{"left":"order.trackingInitialized","operator":"is_true"},"effect":{"type":"block_action","reason":"Tracking already initialized for this order.","id":"588820a5","description":"Effect block_action from a permissions rule on action Initialize tracking."},"description":"System-level cascade; never gated by user auth."}],"invariants":[],"effects":[{"type":"set_state","path":"order.trackingInitialized","value":true,"id":"7b22452f","description":"Effect set_state on action Initialize tracking."},{"type":"set_state","path":"order.status","value":"preparing","id":"733310f6","description":"Effect set_state on action Initialize tracking."},{"type":"set_state","path":"order.statusLabel","value":{"kind":"switch","cases":[{"when":{"left":"order.status","operator":"equals","right":"preparing"},"then":{"kind":"literal","value":"Preparing your order"}},{"when":{"left":"order.status","operator":"equals","right":"shipped"},"then":{"kind":"literal","value":"On its way"}},{"when":{"left":"order.status","operator":"equals","right":"out_for_delivery"},"then":{"kind":"literal","value":"Out for delivery"}},{"when":{"left":"order.status","operator":"equals","right":"delivered"},"then":{"kind":"literal","value":"Delivered"}}],"default":{"kind":"literal","value":"Cancelled"}},"id":"0533fe02","description":"Effect set_state on action Initialize tracking."},{"type":"emit_event","event":"order.tracking.initialized","id":"3409d7f5","description":"Effect emit_event on action Initialize tracking."}],"emittedEvents":["order.tracking.initialized"],"transitions":[],"triggeredByEvent":"order.placed","scenarios":[{"id":"2fe5b481","name":"Cascade: initialize tracking on order.placed","description":"Triggered by the order.placed event in the Cart & checkout feature. Sets the initial status + label via switch Expression.","stateOverrides":[{"path":"carrier.signedIn","value":true},{"path":"order.trackingInitialized","value":false}],"parameterOverrides":[],"expectedStatus":"success","expectedAssertions":[{"path":"order.trackingInitialized","operator":"is_true","description":"Asserts order.trackingInitialized is_true."},{"path":"order.statusLabel","operator":"equals","value":"Preparing your order","description":"Asserts order.statusLabel equals 'Preparing your order'."}]}]},{"id":"70046ae1","name":"Cancel order","intent":"Buyer cancels their order before it ships.","parameters":[],"requiredStates":[],"rules":[{"id":"46706576","category":"permissions","condition":{"left":"user.authenticated","operator":"is_false"},"effect":{"type":"block_action","reason":"Sign in to manage your orders.","id":"2b504026","description":"Effect block_action from a permissions rule on action Cancel order."},"description":"Permissions rule on action Cancel order."},{"id":"c0d778dc","category":"business","condition":{"left":"order.status","operator":"not_equals","right":"preparing"},"effect":{"type":"block_action","reason":"Order has already shipped and cannot be cancelled.","id":"3317ba4c","description":"Effect block_action from a business rule on action Cancel order."},"description":"Only preparing orders can be cancelled."},{"id":"4ec18c0a","category":"validation","condition":{"left":"order.id","operator":"equals","right":""},"effect":{"type":"block_action","reason":"Missing order id.","id":"45cbcacc","description":"Effect block_action from a validation rule on action Cancel order."},"description":"Validation rule on action Cancel order."}],"invariants":[{"id":"baf92824","name":"Cancel leaves order in cancelled state","condition":{"left":"order.status","operator":"equals","right":"cancelled"},"message":"Cancel did not leave the order in the cancelled state.","description":"Action-level post-condition: after Cancel order runs successfully, status must be \\"cancelled\\"."}],"effects":[{"type":"set_state","path":"order.status","value":"cancelled","id":"39add89d","description":"Effect set_state on action Cancel order."},{"type":"set_state","path":"order.statusLabel","value":"Cancelled","id":"0e185a8c","description":"Effect set_state on action Cancel order."},{"type":"emit_event","event":"order.cancelled","id":"4b655d10","description":"Effect emit_event on action Cancel order."}],"emittedEvents":["order.cancelled"],"transitions":[],"scenarios":[{"id":"315705e5","name":"Happy path: cancel preparing order","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"order.status","value":"preparing"}],"parameterOverrides":[],"expectedStatus":"success","expectedAssertions":[{"path":"order.status","operator":"equals","value":"cancelled","description":"Asserts order.status equals 'cancelled'."}],"description":"Scenario Happy path: cancel preparing order on action Cancel order."},{"id":"a18b77eb","name":"Blocked: cannot cancel after shipping","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"order.status","value":"shipped"}],"parameterOverrides":[],"expectedStatus":"blocked","description":"Scenario Blocked: cannot cancel after shipping on action Cancel order."}]},{"id":"b5746e17","name":"Mark as shipped (system)","intent":"System action: order has shipped; status moves to \\"shipped\\".","parameters":[{"id":"5daa85c0","name":"trackingNumber","type":"string","required":true,"description":"Carrier tracking number."}],"requiredStates":[],"rules":[{"id":"e55898b9","category":"permissions","condition":{"left":"carrier.signedIn","operator":"is_false"},"effect":{"type":"block_action","reason":"Carrier authentication required.","id":"07b09ab5","description":"Effect block_action from a permissions rule on action Mark as shipped (system)."},"description":"Only the carrier system can mark shipping."},{"id":"0046ea0a","category":"business","condition":{"left":"order.status","operator":"not_equals","right":"preparing"},"effect":{"type":"block_action","reason":"Order cannot be marked shipped from its current state.","id":"b4e89a6c","description":"Effect block_action from a business rule on action Mark as shipped (system)."},"description":"State machine: must come from preparing."},{"id":"5f0bda62","category":"validation","condition":{"left":"order.id","operator":"equals","right":""},"effect":{"type":"block_action","reason":"Missing order id.","id":"478d5d8b","description":"Effect block_action from a validation rule on action Mark as shipped (system)."},"description":"Validation rule on action Mark as shipped (system)."}],"invariants":[{"id":"605d7dd1","name":"Shipped leaves status shipped","condition":{"left":"order.status","operator":"equals","right":"shipped"},"message":"Mark shipped did not advance the status to shipped.","description":"Action invariant on Mark as shipped (system): Shipped leaves status shipped."}],"effects":[{"type":"set_state","path":"order.status","value":"shipped","id":"17b81e02","description":"Effect set_state on action Mark as shipped (system)."},{"type":"set_state","path":"order.statusLabel","value":"On its way","id":"60ba4f99","description":"Effect set_state on action Mark as shipped (system)."},{"type":"emit_event","event":"order.shipped","id":"766518b8","description":"Effect emit_event on action Mark as shipped (system)."}],"emittedEvents":["order.shipped"],"transitions":[],"scenarios":[{"id":"685d4281","name":"Happy path: order shipped","stateOverrides":[{"path":"carrier.signedIn","value":true},{"path":"order.status","value":"preparing"}],"parameterOverrides":[{"parameterName":"trackingNumber","value":"1Z9999W19999999999"}],"expectedStatus":"success","expectedAssertions":[{"path":"order.status","operator":"equals","value":"shipped","description":"Asserts order.status equals 'shipped'."}],"description":"Scenario Happy path: order shipped on action Mark as shipped (system)."}]},{"id":"9fdd0a83","name":"Mark as out for delivery (carrier)","intent":"Carrier event: order is on the truck for the last mile.","parameters":[],"requiredStates":[],"rules":[{"id":"bb366827","category":"permissions","condition":{"left":"carrier.signedIn","operator":"is_false"},"effect":{"type":"block_action","reason":"Carrier authentication required.","id":"3e42c8b4","description":"Effect block_action from a permissions rule on action Mark as out for delivery (carrier)."},"description":"Permissions rule on action Mark as out for delivery (carrier)."},{"id":"365f9e21","category":"business","condition":{"left":"order.status","operator":"not_equals","right":"shipped"},"effect":{"type":"block_action","reason":"Order is not yet shipped.","id":"c5dcf26a","description":"Effect block_action from a business rule on action Mark as out for delivery (carrier)."},"description":"State machine: must come from shipped."},{"id":"10f5086d","category":"validation","condition":{"left":"order.id","operator":"equals","right":""},"effect":{"type":"block_action","reason":"Missing order id.","id":"81a0c5ca","description":"Effect block_action from a validation rule on action Mark as out for delivery (carrier)."},"description":"Validation rule on action Mark as out for delivery (carrier)."}],"invariants":[{"id":"1f8d3025","name":"Out-for-delivery leaves status out_for_delivery","condition":{"left":"order.status","operator":"equals","right":"out_for_delivery"},"message":"Mark out-for-delivery did not advance the status.","description":"Action invariant on Mark as out for delivery (carrier): Out-for-delivery leaves status out_for_delivery."}],"effects":[{"type":"set_state","path":"order.status","value":"out_for_delivery","id":"e223b29b","description":"Effect set_state on action Mark as out for delivery (carrier)."},{"type":"set_state","path":"order.statusLabel","value":"Out for delivery","id":"8b310c4a","description":"Effect set_state on action Mark as out for delivery (carrier)."},{"type":"emit_event","event":"order.out_for_delivery","id":"0c724e62","description":"Effect emit_event on action Mark as out for delivery (carrier)."}],"emittedEvents":["order.out_for_delivery"],"transitions":[],"scenarios":[{"id":"2bc5885d","name":"Happy path: shipped → out for delivery","stateOverrides":[{"path":"carrier.signedIn","value":true},{"path":"order.status","value":"shipped"}],"parameterOverrides":[],"expectedStatus":"success","description":"Scenario Happy path: shipped → out for delivery on action Mark as out for delivery (carrier)."}]},{"id":"b988e8dc","name":"Confirm delivery","intent":"Buyer confirms receipt of the package.","parameters":[],"requiredStates":[],"rules":[{"id":"51541e34","category":"permissions","condition":{"left":"user.authenticated","operator":"is_false"},"effect":{"type":"block_action","reason":"Sign in to confirm delivery.","id":"ae1c2db4","description":"Effect block_action from a permissions rule on action Confirm delivery."},"description":"Permissions rule on action Confirm delivery."},{"id":"904e7e6a","category":"business","condition":{"left":"order.status","operator":"not_equals","right":"out_for_delivery"},"effect":{"type":"block_action","reason":"Order is not out for delivery.","id":"48d76dfe","description":"Effect block_action from a business rule on action Confirm delivery."},"description":"Business rule on action Confirm delivery."},{"id":"9b224745","category":"validation","condition":{"kind":"all","conditions":[{"left":"delivery.signatureRequired","operator":"is_true"},{"left":"delivery.signatureCollected","operator":"is_false"}]},"effect":{"type":"block_action","reason":"Signature required but not collected.","id":"e377e24f","description":"Effect block_action from a validation rule on action Confirm delivery."},"description":"Composite ALL: if signature is required, it must be collected."}],"invariants":[{"id":"fda7f463","name":"Confirm delivery leaves status delivered","condition":{"left":"order.status","operator":"equals","right":"delivered"},"message":"Confirm delivery did not finalize the order.","description":"Action invariant on Confirm delivery: Confirm delivery leaves status delivered."}],"effects":[{"type":"set_state","path":"order.status","value":"delivered","id":"7e68e970","description":"Effect set_state on action Confirm delivery."},{"type":"set_state","path":"order.statusLabel","value":"Delivered","id":"219b820d","description":"Effect set_state on action Confirm delivery."},{"type":"emit_event","event":"delivery.confirmed","id":"5adacc34","description":"Effect emit_event on action Confirm delivery."},{"type":"emit_event","event":"order.delivered","id":"612d51df","description":"Effect emit_event on action Confirm delivery."}],"emittedEvents":["delivery.confirmed","order.delivered"],"transitions":[],"scenarios":[{"id":"7a1f2032","name":"Happy path: confirm delivery with signature","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"order.status","value":"out_for_delivery"},{"path":"delivery.signatureRequired","value":true},{"path":"delivery.signatureCollected","value":true}],"parameterOverrides":[],"expectedStatus":"success","expectedAssertions":[{"path":"order.status","operator":"equals","value":"delivered","description":"Asserts order.status equals 'delivered'."}],"description":"Scenario Happy path: confirm delivery with signature on action Confirm delivery."},{"id":"abe17979","name":"Blocked: signature required but missing","description":"Composite ALL rule trips.","stateOverrides":[{"path":"user.authenticated","value":true},{"path":"order.status","value":"out_for_delivery"},{"path":"delivery.signatureRequired","value":true},{"path":"delivery.signatureCollected","value":false}],"parameterOverrides":[],"expectedStatus":"blocked"}]}],"rules":[{"id":"05037aef","category":"permissions","condition":{"kind":"all","conditions":[{"left":"user.authenticated","operator":"is_false"},{"left":"carrier.signedIn","operator":"is_false"}]},"effect":{"type":"block_action","reason":"Either a buyer or carrier session is required.","id":"9ba47119","description":"Effect block_action from a surface-level permissions rule on Order tracking & delivery."},"description":"Either the buyer or the carrier may interact with this surface."}],"invariants":[{"id":"12f3ea29","name":"Order id present","condition":{"left":"order.id","operator":"not_equals","right":""},"message":"Order id must be set.","description":"Surface invariant on Order tracking & delivery: Order id present."}],"transitions":[]}],"personas":[{"id":"7e7ff3a9","name":"Buyer awaiting delivery","description":"Order is preparing. Cancellation still possible.","stateOverrides":[{"path":"order.status","value":"preparing"},{"path":"user.authenticated","value":true}],"parameterOverrides":[]},{"id":"068ee6f9","name":"Carrier system","description":"Backend system that pushes carrier status updates.","stateOverrides":[{"path":"carrier.signedIn","value":true}],"parameterOverrides":[]}],"resources":[{"id":"24e275a2","name":"Orders database","description":"Persisted orders + fulfillment state.","kind":"database","provider":"PostgreSQL","scope":"shared","sensitivity":"high","containsPii":true,"complianceTags":[],"accessMode":"read_write"},{"id":"ac3ad07c","name":"Shipping carrier API","description":"Carrier integration for tracking events.","kind":"http_api","provider":"EasyPost","scope":"external","sensitivity":"medium","containsPii":true,"complianceTags":[],"accessMode":"read_write"}],"entities":[{"id":"1dc0be2d","namespace":"order","description":"Order fulfillment record.","fields":[{"id":"0b5ee7ff","name":"status","type":"enum","path":"order.status","description":"Enum field status on entity order."},{"id":"5dcafab0","name":"id","type":"string","path":"order.id","description":"String field id on entity order."},{"id":"7554575f","name":"statusLabel","type":"string","path":"order.statusLabel","description":"String field statusLabel on entity order."}],"resourceId":"24e275a2"}],"events":[{"id":"3d6ef3a8","name":"order.placed","description":"Order successfully placed (emitted by the Cart & checkout feature; subscribed here for cascade init).","payloadSchema":[{"name":"amount","type":"number","required":true,"description":"Number payload field amount on event order.placed."}]},{"id":"fa6c24a5","name":"order.tracking.initialized","description":"Tracking record created for a new order.","payloadSchema":[{"name":"orderId","type":"string","required":true,"description":"String payload field orderId on event order.tracking.initialized."}]},{"id":"9140be16","name":"order.shipped","description":"Order handed off to the carrier.","payloadSchema":[{"name":"orderId","type":"string","required":true,"description":"String payload field orderId on event order.shipped."},{"name":"trackingNumber","type":"string","required":false,"description":"String payload field trackingNumber on event order.shipped."}]},{"id":"f0db2357","name":"order.out_for_delivery","description":"Carrier marked the order out for delivery.","payloadSchema":[{"name":"orderId","type":"string","required":true,"description":"String payload field orderId on event order.out_for_delivery."}]},{"id":"6a13d4d6","name":"order.delivered","description":"Order delivered to the buyer.","payloadSchema":[{"name":"orderId","type":"string","required":true,"description":"String payload field orderId on event order.delivered."}]},{"id":"084f8e4e","name":"order.cancelled","description":"Order cancelled before shipping.","payloadSchema":[{"name":"orderId","type":"string","required":true,"description":"String payload field orderId on event order.cancelled."}]},{"id":"835f563a","name":"delivery.confirmed","description":"Buyer confirmed receipt (with signature if required)."}],"createdAt":"2026-05-18T23:53:41.194Z","updatedAt":"2026-05-19T00:14:55.404Z","featureInvariants":[{"id":"f88f4ada","name":"Status label always set","condition":{"left":"order.statusLabel","operator":"not_equals","right":""},"message":"Order status label must be populated."},{"id":"600aef2f","name":"Status is a known value","condition":{"kind":"any","conditions":[{"left":"order.status","operator":"equals","right":"preparing"},{"left":"order.status","operator":"equals","right":"shipped"},{"left":"order.status","operator":"equals","right":"out_for_delivery"},{"left":"order.status","operator":"equals","right":"delivered"},{"left":"order.status","operator":"equals","right":"cancelled"}]},"message":"Order status drifted to an unknown value.","description":"Composite ANY: status must match one of the 5 valid states."}],"expectedActions":["Initialize tracking","Cancel order","Mark as shipped (system)","Mark as out for delivery (carrier)","Confirm delivery"],"nonGoals":["Live carrier tracking polling","Return label generation","Address verification at delivery time","Re-routing in-flight packages","Photo proof of delivery"]}`),xe={format:ye,version:1,feature:be},Se=e({default:()=>Te,format:()=>Ce,project:()=>we,version:()=>1}),Ce=`unspaghettit-project`,we={id:`f2e35d76`,name:`eShop`,description:`End-to-end e-commerce sample modeled as a Project across four LLM-sized Features: Account & auth, Catalog & reviews, Cart & checkout, Order fulfillment. Designed to showcase the full capability surface of Unspaghettit: composite + Expression conditions, feature invariants, event cascade, bypassInvariants, action invariants, scenarios, persona overrides, entity/resource mapping. Every feature scores 100% maturity.`,featureIds:[`941e4158`,`6abcf051`,`33fbb26d`,`5da6bb5c`],tags:[{type:`domain`,value:`e-commerce`},{type:`kind`,value:`sample`}],createdAt:`2026-05-18T23:53:41.098Z`,updatedAt:`2026-05-18T23:53:41.217Z`},Te={format:Ce,version:1,project:we},Ee=Object.assign({"/samples/eshop/account-auth.feature.json":oe,"/samples/eshop/cart-checkout.feature.json":ue,"/samples/eshop/catalog-reviews.feature.json":me,"/samples/eshop/order-fulfillment.feature.json":ve}),De=Object.assign({"/samples/eshop/eshop.project.json":Se}),Oe=(e,t)=>Object.values(e).map(e=>{let n=e;return n[t]||(n.default?.[t]??void 0)}).filter(e=>!!e),ke=Oe(Ee,`feature`),Ae=Oe(De,`project`),je=`/api/projects`,Me=e=>`${je}/${encodeURIComponent(e)}`,Ne=class{async list(){let e=await h(je);if(!e.ok)throw Error(`Project list failed (${e.status})`);return(await e.json()).summaries}async get(e){let t=await h(Me(e));if(t.status===404)return null;if(!t.ok)throw Error(`Project get failed (${t.status})`);return await t.json()}async save(e){let t=await h(Me(e.id),{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e)});if(!t.ok){let e=await t.text().catch(()=>``);throw Error(`Project save failed (${t.status})${e?`: ${e}`:``}`)}}async delete(e){let t=await h(Me(e),{method:`DELETE`});if(!t.ok&&t.status!==404)throw Error(`Project delete failed (${t.status})`)}},Pe=`/api/domains`,Fe=e=>`${Pe}/${encodeURIComponent(e)}`,Ie=class{async list(){let e=await h(Pe);if(!e.ok)throw Error(`Domain list failed (${e.status})`);return(await e.json()).summaries}async get(e){let t=await h(Fe(e));if(t.status===404)return null;if(!t.ok)throw Error(`Domain get failed (${t.status})`);return await t.json()}async save(e){let t=await h(Fe(e.id),{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e)});if(!t.ok){let e=await t.text().catch(()=>``);throw Error(`Domain save failed (${t.status})${e?`: ${e}`:``}`)}}async delete(e){let t=await h(Fe(e),{method:`DELETE`});if(!t.ok&&t.status!==404)throw Error(`Domain delete failed (${t.status})`)}},Le=e=>`/api/snapshots/${encodeURIComponent(e)}/implementation-status`,Re=class{async get(e){let t=await h(Le(e));return t.ok?await t.json()??null:null}async save(e){throw Error(`HttpImplementationStatusRepository.save is not implemented. Status writes go through the MCP server.`)}async delete(e){throw Error(`HttpImplementationStatusRepository.delete is not implemented.`)}},ze=`/api/tag-palette`,Be=class{async get(){let e=await h(ze);if(!e.ok)throw Error(`Tag palette get failed (${e.status})`);return await e.json()}async save(e){let t=await h(ze,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e)});if(!t.ok){let e=await t.text().catch(()=>``);throw Error(`Tag palette save failed (${t.status})${e?`: ${e}`:``}`)}}},Ve=/^#[0-9a-fA-F]{6}$/,He=e=>typeof e==`string`&&Ve.test(e),Ue=e=>e.toLowerCase(),We={assignments:{}},Ge=e=>e.trim().toLowerCase(),Ke=(e,t,n)=>{let r=Ge(t);if(!r)return e;let i=Ue(n);return e.assignments[r]===i?e:{assignments:{...e.assignments,[r]:i}}},qe=(e,t)=>{let n=Ge(t);return!n||e.assignments[n]===null?e:{assignments:{...e.assignments,[n]:null}}},Je=(e,t)=>e.assignments[Ge(t)],Ye=(e,t)=>Je(e,t)===null,Xe=(e,t)=>{let n=Je(e,t);return typeof n==`string`?n:null},Ze=`/api/tag-palette/rename`,Qe=class{async renameTag(e){let t=await h(Ze,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e)});if(!t.ok){let e=await t.text().catch(()=>``);throw Error(`Tag rename failed (${t.status})${e?`: ${e}`:``}`)}return await t.json()}},$e=()=>new Date().toISOString(),et=()=>{if(typeof crypto<`u`&&typeof crypto.getRandomValues==`function`){let e=new Uint8Array(4);return crypto.getRandomValues(e),Array.from(e,e=>e.toString(16).padStart(2,`0`)).join(``)}return Math.floor(Math.random()*4294967295).toString(16).padStart(8,`0`)},tt=e=>e,nt=e=>e,rt=e=>e,it=e=>e,at=e=>e,ot=e=>e,st=e=>e,ct=e=>e,lt=e=>e,ut=e=>e,dt=e=>e,ft=e=>e,pt=e=>e,mt=e=>e,ht=e=>e,gt=e=>async t=>{let n=t.name.trim();if(n.length===0)throw Error(`Feature name is required`);let r=t.description?.trim()??``;if(r.length===0)throw Error(`Feature description is required`);let i=e.clock(),a={id:tt(e.ids()),name:n,description:r,...b(t.tags)?{tags:b(t.tags)}:{},surfaces:[],personas:[],resources:[],entities:[],events:[],createdAt:i,updatedAt:i};return await e.repository.save(a),a},_t=e=>async t=>{await e.repository.delete(t)},vt=e=>async t=>e.repository.get(t),yt=e=>e.filter(e=>e.evolution===void 0),bt=/^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$/,xt=e=>bt.test(e),St=e=>{if(!xt(e))throw Error(`Invalid state path: ${JSON.stringify(e)}`);return e},Ct=e=>e.split(`.`),S=(e,t)=>{let n=Ct(t),r=e;for(let e of n){if(typeof r!=`object`||!r||Array.isArray(r))return;r=r[e]}return r},wt=e=>{if(Object.keys(e).filter(e=>e.includes(`.`)).length===0)return e;let t={};for(let[n,r]of Object.entries(e))n.includes(`.`)?xt(n)&&(t=C(t,n,r)):t={...t,[n]:r};return t},C=(e,t,n)=>{let r=Ct(t);if(r.length===0)return e;let i=e=>({...e}),a=i(e),o=a;for(let e=0;e<r.length-1;e+=1){let t=r[e],n=o[t],a=n&&typeof n==`object`&&!Array.isArray(n)?i(n):{};o[t]=a,o=a}let s=r[r.length-1];return o[s]=n,a},w=e=>{if(!e||typeof e!=`object`)return!1;let t=e;return t.kind===`param`&&typeof t.name==`string`},T=e=>{if(!e||typeof e!=`object`)return!1;let t=e.kind;return t===`all`||t===`any`||t===`not`},E=e=>{if(!e)return[];let t=[],n=e=>{if(T(e)){if(e.kind===`not`)n(e.condition);else for(let t of e.conditions)n(t);return}t.push(e)};return n(e),t},Tt=new Set([`literal`,`state`,`param`,`add`,`sub`,`mul`,`div`,`mod`,`min`,`max`,`neg`,`not`,`sum`,`count`,`sum_pluck`,`count_where`,`switch`]),D=e=>{if(typeof e!=`object`||!e||Array.isArray(e))return!1;let t=e.kind;return typeof t==`string`&&Tt.has(t)},Et=/^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}(:\d{2}(\.\d{1,3})?)?(Z|[+-]\d{2}:?\d{2})?)?$/,O=e=>typeof e==`string`&&Et.test(e),Dt=(e,t,n)=>{switch(t){case`equals`:return A(e,n);case`not_equals`:return!A(e,n);case`greater_than`:return typeof e==`number`&&typeof n==`number`||O(e)&&O(n)?e>n:!1;case`lower_than`:return typeof e==`number`&&typeof n==`number`||O(e)&&O(n)?e<n:!1;case`contains`:return Array.isArray(e)?e.some(e=>A(e,n)):typeof e==`string`&&typeof n==`string`?e.includes(n):!1;case`is_true`:return e===!0;case`is_false`:return e===!1;case`exists`:return e!=null;case`does_not_exist`:return e==null}},k=(e,t)=>{if(e==null)return!0;if(T(e)){if(e.kind===`not`)return!k(e.condition,t);if(e.kind===`all`){for(let n of e.conditions)if(!k(n,t))return!1;return!0}for(let n of e.conditions)if(k(n,t))return!0;return!1}let n=w(e.left)?t.parameters[e.left.name]:S(t.snapshot,e.left),r=Ot(e.right,t);return Dt(n,e.operator,r)},A=(e,t)=>{if(e===t)return!0;if(e===null||t===null||typeof e!=typeof t||typeof e!=`object`||Array.isArray(e)!==Array.isArray(t))return!1;if(Array.isArray(e)&&Array.isArray(t))return e.length===t.length?e.every((e,n)=>A(e,t[n])):!1;let n=e,r=t,i=Object.keys(n),a=Object.keys(r);return i.length===a.length?i.every(e=>A(n[e],r[e])):!1},j=(e,t)=>{if(!D(e))return e;switch(e.kind){case`literal`:return e.value;case`state`:return S(t.snapshot,e.path);case`param`:return t.parameters[e.name];case`neg`:{let n=j(e.operand,t);return typeof n==`number`?-n:void 0}case`not`:{let n=j(e.operand,t);return n===void 0?void 0:!n}case`sum`:{let n=j(e.operand,t);if(!Array.isArray(n))return;let r=0;for(let e of n){if(typeof e!=`number`)return;r+=e}return r}case`count`:{let n=j(e.operand,t);return Array.isArray(n)?n.length:void 0}case`sum_pluck`:{let n=j(e.operand,t);if(!Array.isArray(n))return;let r=0;for(let t of n){if(!t||typeof t!=`object`||Array.isArray(t))return;let n=t[e.field];if(typeof n!=`number`)return;r+=n}return r}case`count_where`:{let n=j(e.operand,t);if(!Array.isArray(n))return;let r=j(e.equals,t),i=0;for(let t of n){if(!t||typeof t!=`object`||Array.isArray(t))continue;let n=t[e.field];A(n,r)&&(i+=1)}return i}case`switch`:for(let n of e.cases)if(k(n.when,t))return j(n.then,t);return j(e.default,t);case`add`:case`sub`:case`mul`:case`div`:case`mod`:case`min`:case`max`:{let n=j(e.left,t),r=j(e.right,t);if(typeof n!=`number`||typeof r!=`number`)return;switch(e.kind){case`add`:return n+r;case`sub`:return n-r;case`mul`:return n*r;case`div`:return r===0?void 0:n/r;case`mod`:return r===0?void 0:n%r;case`min`:return Math.min(n,r);case`max`:return Math.max(n,r)}}}},Ot=(e,t)=>{if(e!==void 0)return D(e)?j(e,t):e},M=e=>{if(e!==void 0){if(!D(e))return e;switch(e.kind){case`literal`:case`state`:case`param`:return e;case`neg`:return{kind:`neg`,operand:N(e.operand)};case`not`:return{kind:`not`,operand:N(e.operand)};case`sum`:return{kind:`sum`,operand:N(e.operand)};case`count`:return{kind:`count`,operand:N(e.operand)};case`sum_pluck`:return{kind:`sum_pluck`,operand:N(e.operand),field:e.field};case`count_where`:return{kind:`count_where`,operand:N(e.operand),field:e.field,equals:N(e.equals)};case`switch`:return{kind:`switch`,cases:e.cases.map(e=>({when:e.when,then:N(e.then)})),default:N(e.default)};case`add`:case`sub`:case`mul`:case`div`:case`mod`:case`min`:case`max`:return{kind:e.kind,left:N(e.left),right:N(e.right)}}}},N=e=>D(e)?M(e):{kind:`literal`,value:e},kt=()=>({score:0,maxScore:0,percentage:100,criticalIssues:[],recommendedIssues:[],passedChecks:[]}),P=(e,t)=>{let n=e.score+t.score,r=e.maxScore+t.maxScore;return{score:n,maxScore:r,percentage:r===0?100:Math.round(n/r*100),criticalIssues:[...e.criticalIssues,...t.criticalIssues],recommendedIssues:[...e.recommendedIssues,...t.recommendedIssues],passedChecks:[...e.passedChecks,...t.passedChecks]}},F=(e,t,n,r)=>{let i=0,a=0,o=[],s=[],c=[];for(let l of r){a+=l.weight;let r={...n,surfacePanelTab:l.tab??n.surfacePanelTab};if(l.passes){i+=l.weight,c.push({...r,target:t,targetKind:e,area:l.area,message:l.passLabel});continue}let u={...r,target:t,targetKind:e,area:l.area,message:l.message,severity:l.severity};l.severity===`critical`?o.push(u):s.push(u)}return{score:i,maxScore:a,percentage:a===0?100:Math.round(i/a*100),criticalIssues:o,recommendedIssues:s,passedChecks:c}},I=e=>{if(!D(e))return[];switch(e.kind){case`state`:return[e.path];case`add`:case`sub`:case`mul`:case`div`:case`mod`:case`min`:case`max`:return[...I(e.left),...I(e.right)];case`neg`:case`not`:case`sum`:case`count`:return I(e.operand);case`sum_pluck`:return I(e.operand);case`count_where`:return[...I(e.operand),...I(e.equals)];case`switch`:return[...e.cases.flatMap(e=>[...E(e.when).flatMap(e=>[...w(e.left)?[]:[e.left],...D(e.right)?I(e.right):[]]),...I(e.then)]),...I(e.default)];case`literal`:case`param`:return[]}},L=e=>{let t=[];for(let n of E(e))w(n.left)||t.push(n.left),D(n.right)&&t.push(...I(n.right));return t},At=e=>{let t=new Set,n=e=>{for(let n of L(e))t.add(n)},r=e=>{if(e.type===`set_state`&&(t.add(e.path),D(e.value)))for(let n of I(e.value))t.add(n)};for(let t of e.rules)n(t.condition),r(t.effect);for(let t of e.invariants)n(t.condition);for(let t of[...e.effects,...e.onBlockedEffects??[]])r(t);for(let n of e.requiredStates)t.add(n);return[...t]},jt=e=>{let t=new Set;for(let n of e.rules){for(let e of L(n.condition))t.add(e);if(n.effect.type===`set_state`&&(t.add(n.effect.path),D(n.effect.value)))for(let e of I(n.effect.value))t.add(e)}return[...t]},Mt=(e,t)=>{let n=new Set;for(let r of t)for(let t of r.stateDefinitions){let i=String(r.id)===e,a=(t.sharedWith??[]).map(String).includes(e);(i||a)&&n.add(String(t.path))}return n},Nt=e=>{let t=new Set;for(let n of e)for(let e of n.stateDefinitions)t.add(String(e.path));return t},Pt=e=>e.type!==`set_state`||!D(e.value)?[]:I(e.value),Ft=e=>{let t=new Set;for(let n of e.rules)for(let e of L(n.condition))t.add(e);for(let n of e.invariants)for(let e of L(n.condition))t.add(e);for(let n of[...e.effects,...e.onBlockedEffects??[]])for(let e of Pt(n))t.add(e);return[...t]},It=e=>e.type===`set_state`&&D(e.value),Lt=e=>E(e).some(e=>D(e.right)),Rt=e=>e.rules.some(e=>Lt(e.condition))||e.invariants.some(e=>Lt(e.condition))||[...e.effects,...e.onBlockedEffects??[]].some(It),zt=e=>{let t=new Set;for(let n of e.actions){for(let e of[...n.effects,...n.onBlockedEffects??[]])e.type===`transition_surface`&&e.target&&t.add(String(e.target));for(let e of n.rules)e.effect.type===`transition_surface`&&e.effect.target&&t.add(String(e.effect.target))}for(let n of e.rules)n.effect.type===`transition_surface`&&n.effect.target&&t.add(String(n.effect.target));return[...t]},Bt=e=>{let t=new Set;for(let n of e.surfaces){for(let e of n.actions){for(let n of e.emittedEvents)t.add(String(n));for(let n of[...e.effects,...e.onBlockedEffects??[]])n.type===`emit_event`&&t.add(String(n.event));for(let n of e.rules)n.effect.type===`emit_event`&&t.add(String(n.effect.event))}for(let e of n.rules)e.effect.type===`emit_event`&&t.add(String(e.effect.event))}return[...t]},Vt=e=>e.surfaces.some(e=>e.actions.some(e=>e.parameters.some(e=>e.resourceId!==void 0))),Ht=e=>e.surfaces.some(e=>e.actions.some(e=>(e.scenarios??[]).some(e=>e.expectedStatus!==void 0||(e.expectedAssertions?.length??0)>0))),Ut=(e,t,n)=>{let r=new Set([`security`,`permissions`,`compliance`]),i=e.rules.some(e=>r.has(e.category)),a=[...e.effects,...e.onBlockedEffects??[]].some(e=>e.type===`set_state`),o=e.rules.some(e=>e.category===`validation`),s=e.parameters.some(e=>e.validations!==void 0&&e.validations.length>0),c=e.parameters.some(e=>e.type===`string`||e.type===`number`||e.type===`email`||e.type===`url`||e.type===`date`||e.type===`time`||e.type===`timestamp`||e.type===`object`||e.type===`array`||e.type===`geolocation`),l=e.parameters.length>0,u=e.parameters.every(e=>e.required||e.defaultValue!==void 0),d=e.parameters.every(e=>e.type!==`enum`||e.enumValues!==void 0&&e.enumValues.length>0),f=Ft(e),p=e.requiredStates.length===0||e.requiredStates.length>0||f.length===0,m=e.effects.some(e=>e.type===`emit_event`),h=(e.onBlockedEffects??[]).length>0,g=e.rules.length>1||e.effects.length>1||e.invariants.length>0||l||a,_=e.scenarios?.length??0,v=_===0||!g||_>0,y=Rt(e);return[{area:`intent`,weight:1,severity:`critical`,passes:e.intent.trim().length>0,message:`Add a short description of what this action is for.`,passLabel:`Has a description`,tab:`actions`},{area:`rules`,weight:1,severity:`recommended`,passes:e.rules.length>0,message:`Add rules to control when this action can or cannot run.`,passLabel:`Has ${e.rules.length} rule${e.rules.length===1?``:`s`}`,tab:`actions`},{area:`effects`,weight:1,severity:`critical`,passes:e.effects.length>0||e.rules.length>0,message:`Nothing happens when this runs. Add an effect or a rule.`,passLabel:`Has effects or rules`,tab:`actions`},{area:`events`,weight:1,severity:`recommended`,passes:e.emittedEvents.length>0||m,message:`Other parts of your feature cannot react when this runs. Make it emit and declare an event.`,passLabel:e.emittedEvents.length>0&&m?`Emits and declares events`:`Has event signaling`,tab:`actions`},{area:`required states`,weight:1,severity:`recommended`,passes:p,message:`Declare requiredStates so readers and implementation audits know which state this action depends on.`,passLabel:e.requiredStates.length===0?`Required states not declared`:`Documents ${e.requiredStates.length} required state${e.requiredStates.length===1?``:`s`}`,tab:`actions`},{area:`event declarations`,weight:1,severity:`recommended`,passes:!m||e.emittedEvents.length>0,message:`This action emits an event effect but does not declare emittedEvents for audit and discovery.`,passLabel:m?`Declares emitted events`:`No emitted event effects`,tab:`actions`},{area:`permissions`,weight:1,severity:a?`critical`:`recommended`,passes:!a||i,message:`This action changes data. Add a rule that decides who is allowed to run it.`,passLabel:a?`Has a permission rule`:`Does not change data. No permission rule needed`,tab:`actions`},{area:`validation`,weight:1,severity:`recommended`,passes:e.parameters.length===0||!c||o||s,message:`Inputs are not checked before this runs. Add a rule with category "validation" or attach validations to a parameter.`,passLabel:e.parameters.length===0?`No inputs to validate`:c?`Validates its inputs`:`Inputs are booleans/enums. No validation needed`,tab:`actions`},{area:`parameters`,weight:1,severity:`recommended`,passes:!l||u&&d,message:`Make parameter behavior explicit: optional parameters need defaults, and enum parameters need allowed values.`,passLabel:l?`Parameters have defaults/enum options`:`No parameters`,tab:`actions`},{area:`block handling`,weight:1,severity:`recommended`,passes:e.rules.length===0||h||e.rules.some(e=>e.effect.type!==`allow_action`),message:`Add on-block effects when a blocked run should show a message, emit an audit event, or navigate.`,passLabel:e.rules.length===0?`No block paths`:`Has block fallback effects`,tab:`actions`},{area:`scenarios`,weight:1,severity:`recommended`,passes:v,message:`Add at least one scenario for this non-trivial action so examples become regression checks.`,passLabel:g?`Has ${_} scenario${_===1?``:`s`}`:`Scenarios optional until added`,tab:`actions`},{area:`expressions`,weight:1,severity:`recommended`,passes:!y||e.rules.length>0||e.effects.length>0,message:`Expression values should live in explicit rules or effects so readers can inspect the calculation.`,passLabel:y?`Uses expression values`:`No expression values needed`,tab:`actions`},...t===void 0?[]:(()=>{let r=At(e).map(String),i=r.filter(e=>!n.has(e)),a=r.filter(e=>n.has(e)&&!t.has(e));return[{area:`undefined state`,weight:1,severity:`critical`,passes:i.length===0,message:`Uses state ${i.length===1?`path`:`paths`} not declared anywhere in this feature: ${i.slice(0,3).join(`, `)}${i.length>3?`…`:``}. Declare ${i.length===1?`it`:`them`} on a surface.`,passLabel:`All referenced state paths are declared`,tab:`actions`},{area:`unshared state`,weight:1,severity:`recommended`,passes:a.length===0,message:`Reads state ${a.length===1?`path`:`paths`} declared on another surface: ${a.slice(0,3).join(`, `)}${a.length>3?`…`:``}. Add this surface to that StateDefinition's sharedWith to make the dependency explicit.`,passLabel:`No unshared cross-surface reads`,tab:`actions`}]})()]},Wt=(e,t,n)=>{let r=n===void 0?void 0:Mt(String(e.id),n),i=n===void 0?void 0:Nt(n);return F(`action`,t.name,{surfaceId:e.id,actionId:t.id,surfacePanelTab:`actions`},Ut(t,r,i))},Gt=(e,t,n)=>{let r=zt(e),i=e.stateDefinitions.some(e=>e.sharedWith!==void 0&&e.sharedWith.length>0),a=e.stateDefinitions.some(e=>e.type===`enum`&&(e.enumValues===void 0||e.enumValues.length===0)),o=e.actions.length<=1||e.rules.length>0||e.actions.every(e=>e.rules.length>0);return[{area:`actions`,weight:1,severity:`critical`,passes:e.actions.length>0,message:`Add at least one thing the user can do here.`,passLabel:`Has ${e.actions.length} capabilit${e.actions.length===1?`y`:`ies`}`,tab:`actions`},{area:`states`,weight:1,severity:`recommended`,passes:e.stateDefinitions.length>0||t!==void 0&&t.size>0,message:`Add some state - the data your rules read to decide what happens.`,passLabel:`Has ${e.stateDefinitions.length} state value${e.stateDefinitions.length===1?``:`s`}`,tab:`state`},{area:`state metadata`,weight:1,severity:`recommended`,passes:e.stateDefinitions.length===0||!a,message:`Document state definitions with descriptions, and give enum states their allowed values.`,passLabel:e.stateDefinitions.length===0?`No state to document`:`State definitions are documented`,tab:`state`},{area:`shared state`,weight:1,severity:`recommended`,passes:e.stateDefinitions.length<=1||i||e.stateDefinitions.every(e=>e.sharedWith===void 0),message:`Shared state is declared but incomplete. Check sharedWith links on state definitions.`,passLabel:i?`Declares shared state`:`No obvious shared state`,tab:`state`},{area:`surface rules`,weight:1,severity:`recommended`,passes:o,message:`This surface has multiple actions and no visible gating. Add surface rules or action rules.`,passLabel:e.actions.length<=1?`No cross-cutting gate needed`:`Has surface rules`,tab:`rules`},{area:`invariants`,weight:1,severity:`recommended`,passes:e.invariants.length>0||e.actions.some(e=>e.invariants.length>0),message:`Add an invariant - a fact that should always be true here, so broken states are caught early.`,passLabel:`Has invariants`,tab:`invariants`},{area:`transitions`,weight:1,severity:`recommended`,passes:r.length===0||e.transitions.length>0,message:`This surface navigates through effects. Declare transitions so the graph is visible outside individual actions.`,passLabel:r.length===0?`No navigation effects`:`Declares surface transitions`,tab:`actions`},...t===void 0?[]:(()=>{let r=jt(e).map(String),i=r.filter(e=>!n.has(e)),a=r.filter(e=>n.has(e)&&!t.has(e));return[{area:`undefined state in rules`,weight:1,severity:`critical`,passes:i.length===0,message:`Surface rule uses state ${i.length===1?`path`:`paths`} not declared anywhere in this feature: ${i.slice(0,3).join(`, `)}${i.length>3?`…`:``}. Declare ${i.length===1?`it`:`them`} on a surface.`,passLabel:`All surface rule state paths are declared`,tab:`rules`},{area:`unshared state in rules`,weight:1,severity:`recommended`,passes:a.length===0,message:`Surface rule reads state ${a.length===1?`path`:`paths`} declared on another surface: ${a.slice(0,3).join(`, `)}${a.length>3?`…`:``}. Add this surface to that StateDefinition's sharedWith.`,passLabel:`No unshared cross-surface reads in surface rules`,tab:`rules`}]})()]},Kt=(e,t)=>{let n=t===void 0?void 0:Mt(String(e.id),t),r=t===void 0?void 0:Nt(t),i=yt(e.actions);return P(F(`surface`,e.name,{surfaceId:e.id},Gt({...e,actions:i},n,r)),i.reduce((n,r)=>P(n,Wt(e,r,t)),kt()))},qt=e=>{let t={area:`name`,weight:1,severity:`critical`,passes:e.name.trim().length>0,message:`Give this feature a name.`,passLabel:`Has a name`},n={area:`surfaces`,weight:1,severity:`critical`,passes:e.surfaces.length>0,message:`Add a surface - a place where users see and do things.`,passLabel:`Has ${e.surfaces.length} surface${e.surfaces.length===1?``:`s`}`};if(e.surfaces.length===0)return[t,n];let r=Bt(e),i=new Set((e.events??[]).map(e=>String(e.name))),a=r.every(e=>i.has(e)),o=(e.events??[]).every(e=>(e.payloadSchema?.length??0)===0||e.payloadSchema?.every(e=>e.name.trim().length>0)),s=Vt(e),c=e.surfaces.flatMap(e=>e.actions.flatMap(e=>e.scenarios??[]));return[t,{area:`description`,weight:1,severity:`recommended`,passes:!0,message:`Add an feature description so agents and humans know the product boundary.`,passLabel:(e.description?.trim().length??0)>0?`Has a description`:`Description optional`},n,{area:`events registry`,weight:1,severity:`recommended`,passes:r.length===0||(e.events??[]).length===0||a,message:`Register emitted events in the Events tab so payloads and producers are discoverable.`,passLabel:r.length===0?`No emitted events to register`:(e.events??[]).length===0?`Event registry optional until used`:`Emitted events are registered`},{area:`event payloads`,weight:1,severity:`recommended`,passes:o,message:`Event payload schema fields need names when payloads are declared.`,passLabel:(e.events??[]).length===0?`No event payloads declared`:`Payload schemas are valid`},{area:`resources`,weight:1,severity:`recommended`,passes:!s||e.resources.length>0,message:`Parameters reference resources, but no Resources are declared at the feature level.`,passLabel:s?`Declares referenced resources`:`No resource references`},{area:`data`,weight:1,severity:`recommended`,passes:!0,message:`Promote repeated or domain-shaped state into Entity definitions so the model has a clear schema.`,passLabel:e.entities.length>0?`Has data definitions`:`Entity definitions optional`},{area:`personas`,weight:1,severity:`recommended`,passes:!0,message:`Add personas to exercise important roles, regions, plans, or auth states.`,passLabel:e.personas.length>0?`Has personas`:`Personas optional`},{area:`scenario assertions`,weight:1,severity:`recommended`,passes:c.length===0||Ht(e)||c.length>0,message:`Scenarios are more useful when they declare expectedStatus or expectedAssertions.`,passLabel:c.length===0?`No scenarios yet`:Ht(e)?`Scenarios have expectations`:`Scenario expectations optional`},{area:`implementation context`,weight:1,severity:`recommended`,passes:!0,message:`Add devContext so MCP implementation audits can reflect the stack and coding conventions.`,passLabel:e.devContext?`Has implementation context`:`Implementation context optional`}]},Jt=e=>({...e,score:0,percentage:0,passedChecks:[]}),R=e=>{let t=F(`feature`,e.name,{},qt(e));return e.surfaces.length===0?Jt(t):P(t,e.surfaces.reduce((t,n)=>P(t,Kt(n,e.surfaces)),kt()))},Yt=(e,t)=>{let n=t===void 0?void 0:Mt(String(e.id),t),r=t===void 0?void 0:Nt(t),i=yt(e.actions),a=F(`surface`,e.name,{surfaceId:e.id},Gt({...e,actions:i},n,r)),o=i.map(n=>({action:n,report:Wt(e,n,t)}));return{surface:e,aggregate:o.reduce((e,t)=>P(e,t.report),a),surfaceLevel:a,perAction:o}},Xt=e=>{let t=F(`feature`,e.name,{},qt(e));if(e.surfaces.length===0)return{featureLevel:t,perSurface:[],global:Jt(t)};let n=e.surfaces.map(t=>{let n=Yt(t,e.surfaces);return{surface:n.surface,report:n.aggregate,surfaceLevel:n.surfaceLevel,perAction:n.perAction}});return{featureLevel:t,perSurface:n,global:n.reduce((e,t)=>P(e,t.report),t)}},Zt=e=>{let t=e.trim().toLowerCase();return t.length===0?``:t.replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).replace(/-+/g,`-`)},z=(e,t)=>t===0?100:Math.round(e/t*100),Qt=e=>{let t=Zt(e.name)||String(e.id),n=[{entityType:`action`,entityId:String(e.id),entityName:e.name,tag:`@unspa:action:${t}`}];for(let r of e.emittedEvents){let e=String(r);n.push({entityType:`event`,entityId:e,entityName:e,tag:`@unspa:${t}#${e}`})}for(let t of e.rules)n.push({entityType:`rule`,entityId:String(t.id),entityName:t.description,tag:`@unspa:rule:${String(t.id)}`});for(let t of e.invariants){let e=Zt(t.name)||String(t.id);n.push({entityType:`invariant`,entityId:String(t.id),entityName:t.name,tag:`@unspa:invariant:${e}`})}for(let t of e.transitions)n.push({entityType:`transition`,entityId:String(t.id),entityName:t.label,tag:`@unspa:transition:${String(t.id)}`});return n},$t=e=>{let t=[];for(let n of e.stateDefinitions){let e=String(n.path);t.push({entityType:`state`,entityId:String(n.id),entityName:e,tag:`@unspa:state:${e}`})}for(let n of e.rules)t.push({entityType:`surface_rule`,entityId:String(n.id),entityName:n.description,tag:`@unspa:surface_rule:${String(n.id)}`});for(let n of e.invariants){let e=Zt(n.name)||String(n.id);t.push({entityType:`surface_invariant`,entityId:String(n.id),entityName:n.name,tag:`@unspa:surface_invariant:${e}`})}return t},B=(e,t)=>`${e}:${t}`,en=(e,t)=>{let n=Qt(e),r=new Set(n.map(e=>B(e.entityType,e.entityId)));if(!t)return{action:e,status:null,expected:n,found:[],missing:n.map(e=>({entityType:e.entityType,entityId:e.entityId,entityName:e.entityName,tag:e.tag})),expectedCount:n.length,foundCount:0,missingCount:n.length,extraCount:0,percentage:n.length===0?100:0,reported:!1};let i=t.foundEntities.filter(e=>r.has(B(e.entityType,e.entityId))),a=new Set(i.map(e=>B(e.entityType,e.entityId))),o=n.filter(e=>!a.has(B(e.entityType,e.entityId))).map(e=>({entityType:e.entityType,entityId:e.entityId,entityName:e.entityName,tag:e.tag}));return{action:e,status:t,expected:n,found:i,missing:o,expectedCount:n.length,foundCount:i.length,missingCount:o.length,extraCount:t.extraTags.length,percentage:z(i.length,n.length),reported:!0}},tn=(e,t)=>{let n=$t(e),r=new Set(n.map(e=>B(e.entityType,e.entityId)));if(!t)return{status:null,expected:n,found:[],missing:n.map(e=>({entityType:e.entityType,entityId:e.entityId,entityName:e.entityName,tag:e.tag})),expectedCount:n.length,foundCount:0,missingCount:n.length,extraCount:0,percentage:n.length===0?100:0,reported:!1};let i=t.foundEntities.filter(e=>r.has(B(e.entityType,e.entityId))),a=new Set(i.map(e=>B(e.entityType,e.entityId))),o=n.filter(e=>!a.has(B(e.entityType,e.entityId))).map(e=>({entityType:e.entityType,entityId:e.entityId,entityName:e.entityName,tag:e.tag}));return{status:t,expected:n,found:i,missing:o,expectedCount:n.length,foundCount:i.length,missingCount:o.length,extraCount:t.extraTags.length,percentage:z(i.length,n.length),reported:!0}},nn=(e,t)=>{let n=new Map,r=new Map;if(t){for(let e of t.actions)n.set(String(e.actionId),e);for(let e of t.surfaces)r.set(String(e.surfaceId),e)}let i=0,a=0,o=0,s=0,c=0,l=0,u=e.surfaces.map(e=>{let t=e.actions.map(e=>en(e,n.get(String(e.id))??null)),u=tn(e,r.get(String(e.id))??null),d=u.expectedCount,f=u.foundCount,p=u.missingCount,m=u.extraCount,h=0,g=0;for(let e of t)d+=e.expectedCount,f+=e.foundCount,p+=e.missingCount,m+=e.extraCount,e.expectedCount>0&&(g+=1),e.reported&&(h+=1);return i+=d,a+=f,o+=p,s+=m,c+=h,l+=g,{surface:e,surfaceLevel:u,perAction:t,expectedCount:d,foundCount:f,missingCount:p,extraCount:m,percentage:z(f,d),reportedCount:h,capabilitiesWithExpected:g}});return{hasReport:t!==null,revision:t?.revision??0,updatedAt:t?.updatedAt??null,perSurface:u,expectedCount:i,foundCount:a,missingCount:o,extraCount:s,percentage:z(a,i),reportedCount:c,capabilitiesWithExpected:l}},rn=e=>async t=>{let[n,r]=await Promise.all([e.repository.get(t),e.statusRepository.get(t)]);if(!n)return null;let i=R(n),a=nn(n,r);return{id:n.id,name:n.name,description:n.description,tags:n.tags,surfaceCount:n.surfaces.length,actionCount:n.surfaces.reduce((e,t)=>e+t.actions.length,0),createdAt:n.createdAt,updatedAt:n.updatedAt,maturityPercentage:i.percentage,criticalIssueCount:i.criticalIssues.length,recommendedIssueCount:i.recommendedIssues.length,implementationPercentage:a.expectedCount===0?null:a.percentage,implementationFoundCount:a.foundCount,implementationExpectedCount:a.expectedCount,implementationHasReport:a.hasReport}},an=e=>async()=>e.repository.list(),on=e=>async()=>{let t=await e.repository.list();return await Promise.all(t.map(async t=>{let[n,r]=await Promise.all([e.repository.get(t.id),e.statusRepository.get(t.id)]);if(!n)return{...t,maturityPercentage:100,criticalIssueCount:0,recommendedIssueCount:0,implementationPercentage:null,implementationFoundCount:0,implementationExpectedCount:0,implementationHasReport:!1};let i=R(n),a=nn(n,r);return{...t,maturityPercentage:i.percentage,criticalIssueCount:i.criticalIssues.length,recommendedIssueCount:i.recommendedIssues.length,implementationPercentage:a.expectedCount===0?null:a.percentage,implementationFoundCount:a.foundCount,implementationExpectedCount:a.expectedCount,implementationHasReport:a.hasReport}}))},sn=e=>async()=>{let t=[],n=[],r=[],i=[];for(let r of e.samples){if(await e.repository.get(r.id)){n.push({id:String(r.id),name:r.name});continue}await e.repository.save(r),t.push({id:String(r.id),name:r.name})}for(let t of e.projectSamples){if(await e.projectRepository.get(t.id)){i.push({id:String(t.id),name:t.name});continue}await e.projectRepository.save(t),r.push({id:String(t.id),name:t.name})}return{addedProjects:r,addedFeatures:t,skippedProjects:i,skippedFeatures:n}},cn=(e,t)=>e.valueSetId===void 0?e.enumValues:t?.find(t=>t.id===e.valueSetId)?.values,ln=/^[a-z][a-z0-9_]*(\.[a-z0-9_]+)+$/,V=e=>ln.test(e),un=e=>{if(!V(e))throw Error(`Invalid event name "${e}". Use lowercase dot-separated form like "selection.deleted".`);return e},dn=[`string`,`number`,`boolean`,`enum`,`object`,`array`,`date`,`time`,`timestamp`,`url`,`email`,`geolocation`],H=e=>{switch(e){case`date`:case`time`:case`timestamp`:case`url`:case`email`:return`string`;case`geolocation`:return`object`;default:return e}},fn=[`business`,`security`,`permissions`,`compliance`,`validation`,`data`,`ux_feedback`,`error_handling`,`async`,`collaboration`,`billing_quota`,`audit`],pn=e=>{switch(e){case`business`:return`Business`;case`security`:return`Security`;case`permissions`:return`Permissions`;case`compliance`:return`Compliance`;case`validation`:return`Validation`;case`data`:return`Entity`;case`ux_feedback`:return`UX Feedback`;case`error_handling`:return`Error handling`;case`async`:return`Async`;case`collaboration`:return`Collaboration`;case`billing_quota`:return`Billing / Quota`;case`audit`:return`Audit`}},U={permission:`permissions`,behavior:`business`,precondition:`validation`,rendering:`ux_feedback`,ui:`ux_feedback`,visibility:`ux_feedback`},mn=new Set(fn),hn=e=>{let t=String(e);return mn.has(t)?t:t in U?U[t]:t},gn=e=>{let t=hn(e.category);return t===e.category?e:{...e,category:t}},_n=e=>{let t=!1,n=e.rules.map(e=>{let n=gn(e);return n!==e&&(t=!0),n});return t?{...e,rules:n}:e},vn=e=>{let t=!1,n=e.rules.map(e=>{let n=gn(e);return n!==e&&(t=!0),n}),r=e.actions.map(e=>{let n=_n(e);return n!==e&&(t=!0),n});return t?{...e,rules:n,actions:r}:e},yn=e=>({...e,surfaces:e.surfaces.map(vn)}),bn=e=>{if(e===null)return`null`;if(Array.isArray(e))return`array`;let t=typeof e;return t===`string`||t===`number`||t===`boolean`?t:`object`},xn=(e,t)=>{if(e===null)return t!==`enum`;switch(t){case`string`:return typeof e==`string`;case`number`:return typeof e==`number`&&Number.isFinite(e);case`boolean`:return typeof e==`boolean`;case`enum`:return typeof e==`string`;case`object`:return typeof e==`object`&&!Array.isArray(e);case`array`:return Array.isArray(e)}},Sn=new Set(fn),Cn=e=>e in U?`Did you mean "${U[e]}"?`:`Valid categories: ${[...Sn].join(`, `)}.`,wn=e=>typeof e==`string`?`"${e}"`:JSON.stringify(e),Tn=(e,t)=>{let n=bn(e),r=`Received ${wn(e)} (${n}); expected ${t}.`;return typeof e==`string`?t===`boolean`&&(e===`true`||e===`false`)?`${r} Use ${e} instead of "${e}".`:t===`number`&&e.trim()!==``&&Number.isFinite(Number(e))?`${r} Use ${Number(e)} instead of "${e}".`:t===`array`&&e.trim().startsWith(`[`)?`${r} Use a JSON array value, not a JSON-encoded string.`:t===`object`&&e.trim().startsWith(`{`)?`${r} Use a JSON object value, not a JSON-encoded string.`:r:r},En=e=>typeof e?.description==`string`&&e.description.trim().length>0,W=(e,t,n)=>{En(n)||e.push(`${t} is missing a description.`)},Dn=e=>{let t=[],n=new Set;for(let r of e.personas)n.has(r.id)&&t.push(`Duplicate persona id "${r.id}"`),n.add(r.id),W(t,`Persona ${r.id}`,r);let r=new Set;for(let n of e.valueSets??[])r.has(String(n.id))&&t.push(`Duplicate value set id "${n.id}"`),r.add(String(n.id)),(!n.name||n.name.trim().length===0)&&t.push(`Value set ${n.id} is missing a name.`),W(t,`Value set ${n.id}`,n),!Array.isArray(n.values)||n.values.length===0?t.push(`Value set ${n.id} ("${n.name}") must declare at least one value.`):new Set(n.values).size!==n.values.length&&t.push(`Value set ${n.id} ("${n.name}") has duplicate values.`);(!e.id||e.id.trim().length===0)&&t.push(`Feature id is required.`),(!e.name||e.name.trim().length===0)&&t.push(`Feature name is required.`),W(t,`Feature ${e.id||`(new)`}`,e);let i=new Set;for(let n of e.surfaces)i.has(n.id)&&t.push(`Duplicate surface id: ${n.id}`),i.add(n.id),(!n.name||n.name.trim().length===0)&&t.push(`Surface ${n.id} is missing a name.`),W(t,`Surface ${n.id}`,n);for(let a of e.surfaces){a.parentSurfaceId&&(i.has(a.parentSurfaceId)||t.push(`Surface ${a.id} parentSurfaceId "${a.parentSurfaceId}" does not exist.`),a.parentSurfaceId===a.id&&t.push(`Surface ${a.id} cannot be its own parent.`));let o=new Set,s=new Set;for(let n of a.stateDefinitions){if(o.has(n.id)&&t.push(`Duplicate stateDefinition id "${n.id}" in surface ${a.id}`),o.add(n.id),s.has(n.path)&&t.push(`Duplicate state path "${n.path}" in surface ${a.id}. Only one stateDefinition per path is allowed.`),s.add(n.path),W(t,`State definition "${n.path}" in surface ${a.id}`,n),!xn(n.defaultValue,n.type))t.push(`State "${n.path}" in surface ${a.id}: defaultValue is not assignable to declared type "${n.type}". ${Tn(n.defaultValue,n.type)}`);else if(n.type===`enum`){n.enumValues&&n.valueSetId!==void 0&&t.push(`State "${n.path}" in surface ${a.id}: set either enumValues or valueSetId, not both.`),n.valueSetId!==void 0&&!r.has(String(n.valueSetId))&&t.push(`State "${n.path}" in surface ${a.id}: valueSetId "${n.valueSetId}" does not resolve to a value set on the feature.`);let i=cn(n,e.valueSets);i&&typeof n.defaultValue==`string`&&!i.includes(n.defaultValue)&&t.push(`State "${n.path}" in surface ${a.id}: defaultValue "${n.defaultValue}" is not one of ${n.valueSetId===void 0?`enumValues`:`value set "${n.valueSetId}"`}.`)}if(n.valueSetId!==void 0&&n.type!==`enum`&&t.push(`State "${n.path}" in surface ${a.id}: valueSetId is only valid for type "enum" (got "${n.type}").`),n.sharedWith)for(let e of n.sharedWith)e===a.id?t.push(`State "${n.path}" sharedWith references its own surface ${a.id}. Drop it.`):i.has(e)||t.push(`State "${n.path}" in surface ${a.id}: sharedWith references unknown surface "${e}".`)}let c=new Set;for(let e of a.rules)c.has(e.id)&&t.push(`Duplicate surface rule id "${e.id}" in surface ${a.id}`),c.add(e.id),W(t,`Surface rule ${e.id} in surface ${a.id}`,e),Sn.has(String(e.category))||t.push(`Surface rule ${e.id} in surface ${a.id} has unknown category "${e.category}". ${Cn(String(e.category))}`);let l=new Set;for(let e of a.invariants)l.has(e.id)&&t.push(`Duplicate surface invariant id "${e.id}" in surface ${a.id}`),l.add(e.id),W(t,`Surface invariant ${e.id} in surface ${a.id}`,e);let u=new Set;for(let e of a.transitions)u.has(e.id)&&t.push(`Duplicate transition id "${e.id}" in surface ${a.id}`),u.add(e.id),W(t,`Transition ${e.id} in surface ${a.id}`,e),i.has(e.target)||t.push(`Transition ${e.id} in surface ${a.id} targets unknown surface "${e.target}"`);let d=new Set;for(let i of a.actions){d.has(i.id)&&t.push(`Duplicate action id "${i.id}" in surface ${a.id}`),d.add(i.id),(!i.name||i.name.trim().length===0)&&t.push(`Action ${i.id} is missing a name.`),(!i.intent||i.intent.trim().length===0)&&t.push(`Action ${i.id} is missing an intent.`);let o=new Set;for(let n of i.parameters){if(o.has(n.id)&&t.push(`Duplicate parameter id "${n.id}" in action ${i.id}`),o.add(n.id),W(t,`Parameter "${n.name}" in action ${i.id}`,n),n.defaultValue!==void 0&&!xn(n.defaultValue,H(n.type))){let e=H(n.type);t.push(`Parameter "${n.name}" in action ${i.id}: defaultValue is not assignable to declared type "${n.type}". ${Tn(n.defaultValue,e)}`)}else if(n.type===`enum`){n.enumValues&&n.valueSetId!==void 0&&t.push(`Parameter "${n.name}" in action ${i.id}: set either enumValues or valueSetId, not both.`),n.valueSetId!==void 0&&!r.has(String(n.valueSetId))&&t.push(`Parameter "${n.name}" in action ${i.id}: valueSetId "${n.valueSetId}" does not resolve to a value set on the feature.`);let a=cn(n,e.valueSets);a&&typeof n.defaultValue==`string`&&!a.includes(n.defaultValue)&&t.push(`Parameter "${n.name}" in action ${i.id}: defaultValue "${n.defaultValue}" is not one of ${n.valueSetId===void 0?`enumValues`:`value set "${n.valueSetId}"`}.`)}n.valueSetId!==void 0&&n.type!==`enum`&&t.push(`Parameter "${n.name}" in action ${i.id}: valueSetId is only valid for type "enum" (got "${n.type}").`)}let s=new Set;for(let e of i.rules)s.has(e.id)&&t.push(`Duplicate rule id "${e.id}" in action ${i.id}`),s.add(e.id),W(t,`Rule ${e.id} in action ${i.id}`,e),Sn.has(String(e.category))||t.push(`Rule ${e.id} in action ${i.id} has unknown category "${e.category}". ${Cn(String(e.category))}`);let c=new Set;for(let e of i.effects)c.has(e.id)&&t.push(`Duplicate effect id "${e.id}" in action ${i.id}`),c.add(e.id),W(t,`Effect ${e.id} in action ${i.id}`,e);let l=new Set;for(let e of i.onBlockedEffects??[])l.has(e.id)&&t.push(`Duplicate onBlockedEffect id "${e.id}" in action ${i.id}`),l.add(e.id),W(t,`onBlockedEffect ${e.id} in action ${i.id}`,e);let u=new Set;for(let e of i.invariants)u.has(e.id)&&t.push(`Duplicate invariant id "${e.id}" in action ${i.id}`),u.add(e.id),W(t,`Invariant ${e.id} in action ${i.id}`,e);let f=new Set,p=new Set(i.parameters.map(e=>e.name));for(let r of i.scenarios??[]){f.has(r.id)&&t.push(`Duplicate scenario id "${r.id}" in action ${i.id}`),f.add(r.id),W(t,`Scenario ${r.id} in action ${i.id}`,r),r.personaId&&!n.has(String(r.personaId))&&t.push(`Scenario ${r.id} in action ${i.id} references unknown personaId "${r.personaId}".`);for(let e of r.parameterOverrides??[])(typeof e.parameterName!=`string`||!p.has(e.parameterName))&&t.push(`Scenario ${r.id} in action ${i.id}: parameterOverrides entry references unknown parameter "${String(e.parameterName)}". Declared parameters: ${i.parameters.length===0?`<none>`:i.parameters.map(e=>e.name).join(`, `)}.`);for(let e of r.expectedAssertions??[])W(t,`Scenario assertion for "${e.path}" in scenario ${r.id}`,e);for(let[n,o]of(r.steps??[]).entries()){let s=o.surfaceId===void 0?a.id:o.surfaceId,c=e.surfaces.find(e=>e.id===s);if(!c){t.push(`Scenario ${r.id} in action ${i.id}: step ${n} references unknown surfaceId "${String(o.surfaceId)}".`);continue}let l=c.actions.find(e=>e.id===o.actionId);if(!l){t.push(`Scenario ${r.id} in action ${i.id}: step ${n} references unknown actionId "${String(o.actionId)}" on surface ${String(s)}.`);continue}let u=new Set(l.parameters.map(e=>e.name));for(let e of o.parameterOverrides??[])(typeof e.parameterName!=`string`||!u.has(e.parameterName))&&t.push(`Scenario ${r.id} in action ${i.id}: step ${n} parameterOverrides references unknown parameter "${String(e.parameterName)}" on action ${String(o.actionId)}. Declared parameters: ${l.parameters.length===0?`<none>`:l.parameters.map(e=>e.name).join(`, `)}.`);for(let e of o.expectedAssertions??[])W(t,`Scenario ${r.id} step ${n} assertion for "${e.path}"`,e)}}}}let a=new Set;for(let n of e.resources)a.has(n.id)&&t.push(`Duplicate resource id "${n.id}"`),a.add(n.id),W(t,`Resource ${n.id}`,n);let o=new Set;for(let n of e.entities){o.has(n.id)&&t.push(`Duplicate data id "${n.id}"`),o.add(n.id),W(t,`Entity "${n.namespace}"`,n);let e=r=>{W(t,`Entity field "${r.name}" in entity ${n.namespace}`,r);for(let t of r.fields??[])e(t);r.items&&e(r.items)};for(let t of n.fields)e(t)}let s=new Set,c=new Set;for(let n of e.events??[]){s.has(n.id)&&t.push(`Duplicate event id "${n.id}"`),s.add(n.id),W(t,`Event ${n.id}`,n);for(let e of n.payloadSchema??[])W(t,`Event payload field "${e.name}" in event ${n.id}`,e);let e=String(n.name);c.has(e)&&t.push(`Duplicate event name "${e}". Event names must be unique within an feature.`),c.add(e),V(e)||t.push(G(e,`Event ${n.id}`))}for(let n of e.surfaces){for(let e of n.actions){for(let n of e.emittedEvents){let r=String(n);V(r)||t.push(G(r,`Action ${e.id} emittedEvents`))}let n=(e,n)=>{if(e.type===`emit_event`&&e.event!==void 0){let r=String(e.event);V(r)||t.push(G(r,n))}};for(let t of e.effects)n(t,`Effect in action ${e.id}`);for(let t of e.onBlockedEffects??[])n(t,`onBlockedEffect in action ${e.id}`);for(let t of e.rules)n(t.effect,`Rule ${t.id} effect in action ${e.id}`)}for(let e of n.rules)if(e.effect.type===`emit_event`&&e.effect.event!==void 0){let n=String(e.effect.event);V(n)||t.push(G(n,`Surface rule ${e.id} effect`))}}return t.length===0?{valid:!0}:{valid:!1,errors:t}},G=(e,t)=>`${t}: invalid event name "${e}". Use lowercase dot-separated form like "selection.deleted". Example fixes: "${On(e)}".`,On=e=>e.replace(/([a-z0-9])([A-Z])/g,`$1_$2`).toLowerCase().replace(/[^a-z0-9._]/g,`_`).replace(/_+/g,`_`).replace(/^_+|_+$/g,``),K=(e,t)=>{if(D(e))switch(e.kind){case`literal`:case`param`:return;case`state`:t.push(e.path);return;case`neg`:case`not`:case`sum`:case`count`:case`sum_pluck`:K(e.operand,t);return;case`count_where`:K(e.operand,t),K(e.equals,t);return;case`switch`:for(let n of e.cases){for(let e of E(n.when))w(e.left)||t.push(e.left),D(e.right)&&K(e.right,t);K(n.then,t)}K(e.default,t);return;default:K(e.left,t),K(e.right,t)}},kn=e=>{if(!D(e))return[];let t=[];return K(e,t),t},An=e=>{if(!D(e))return[];let t=[];return K(e,t),t},jn=e=>{let t=[],n=new Set;for(let t of e.surfaces)n.add(t.id);let r=new Set;for(let t of e.events??[])r.add(String(t.name));let i=r.size>0,a=new Map;for(let t of e.surfaces){let e=new Set;for(let n of t.stateDefinitions)e.add(n.path);a.set(t.id,e)}let o=new Map;for(let t of e.surfaces)o.set(t.id,new Set);for(let t of e.surfaces)for(let e of t.stateDefinitions)for(let t of e.sharedWith??[]){let n=o.get(t);n&&n.add(e.path)}let s=e=>{let t=a.get(e)??new Set,n=o.get(e)??new Set;return new Set([...t,...n])},c=new Set([`set_state`,`show_message`,`emit_event`,`block_action`,`allow_action`,`transition_surface`]),l=(e,a,o,s)=>{if(!c.has(e.type)){t.push(`${s} effect ${e.id}: unknown effect.type "${e.type}". Valid: set_state, show_message, emit_event, block_action, allow_action, transition_surface.`);return}switch(e.type){case`set_state`:{let n=e.path;typeof n==`string`&&!o.has(n)&&t.push(`${s} effect ${e.id}: set_state path "${n}" is not declared on surface ${a} (or shared into it).`);for(let n of An(e.value))o.has(n)||t.push(`${s} effect ${e.id}: set_state value references unknown state path "${n}" on surface ${a}.`);return}case`emit_event`:{let n=e.event;i&&typeof n==`string`&&!r.has(n)&&t.push(`${s} effect ${e.id}: emit_event "${n}" is not registered in feature.events.`);return}case`transition_surface`:{let r=e.target;typeof r==`string`&&!n.has(r)&&t.push(`${s} effect ${e.id}: transition_surface target "${r}" is not a known surface id.`);return}}},u=(e,n,r,i,a)=>{if(e)for(let o of E(e)){w(o.left)?a?a.has(o.left.name)||t.push(`${i}: condition.left references parameter "${o.left.name}" but the action has no parameter with that name.`):t.push(`${i}: condition.left references parameter "${o.left.name}" but this scope has no parameters. Param-on-left only works for action rules / action invariants.`):typeof o.left==`string`?n.has(o.left)||t.push(`${i}: condition.left "${o.left}" is not a declared state path on surface ${r}.`):t.push(`${i}: condition.left must be a state-path string or a {kind:"param", name} object (got ${JSON.stringify(o.left)}).`);for(let e of kn(o.right))n.has(e)||t.push(`${i}: condition.right references unknown state path "${e}" on surface ${r}.`)}};for(let n of e.surfaces){let e=s(n.id);for(let t of n.rules){let r=`Surface ${n.id} rule ${t.id}`;u(t.condition,e,n.id,r),l(t.effect,n.id,e,r)}for(let t of n.invariants)u(t.condition,e,n.id,`Surface ${n.id} invariant ${t.id}`);for(let a of n.actions){for(let r of a.requiredStates){if(typeof r!=`string`){t.push(`Action ${a.id} on surface ${n.id}: requiredStates entries must be state-path strings (got ${JSON.stringify(r)}). Use rules or invariants if you need conditions on those paths.`);continue}e.has(r)||t.push(`Action ${a.id} on surface ${n.id}: requiredStates references unknown state path "${r}".`)}if(i)for(let e of a.emittedEvents)r.has(String(e))||t.push(`Action ${a.id} on surface ${n.id}: emittedEvents contains "${e}" which is not registered in feature.events.`);if(a.triggeredByEvent!==void 0){let e=String(a.triggeredByEvent);i&&!r.has(e)&&t.push(`Action ${a.id} on surface ${n.id}: triggeredByEvent "${e}" is not registered in feature.events.`);for(let r of a.parameters)r.required&&r.defaultValue===void 0&&t.push(`Action ${a.id} on surface ${n.id}: handler (triggeredByEvent="${e}") has required parameter "${r.name}" with no default. Handlers receive no input from cascades; give the parameter a default or make it optional.`)}let o=new Set(a.parameters.map(e=>e.name));for(let t of a.rules){let r=`Action ${a.id} rule ${t.id}`;u(t.condition,e,n.id,r,o),l(t.effect,n.id,e,r)}for(let t of a.invariants)u(t.condition,e,n.id,`Action ${a.id} invariant ${t.id}`,o);for(let t of a.effects)l(t,n.id,e,`Action ${a.id}`);for(let t of a.onBlockedEffects??[])l(t,n.id,e,`Action ${a.id} onBlocked`)}}let d=new Set;for(let t of e.surfaces)for(let e of t.stateDefinitions)d.add(String(e.path));for(let n of e.featureInvariants??[])for(let e of E(n.condition)){if(w(e.left)){t.push(`Feature invariant ${n.id}: condition.left references parameter "${e.left.name}" but feature invariants run outside any action scope and have no parameters available.`);continue}d.has(e.left)||t.push(`Feature invariant ${n.id}: condition.left "${e.left}" is not declared on any surface in the feature.`);for(let r of kn(e.right))d.has(r)||t.push(`Feature invariant ${n.id}: condition.right references unknown state path "${r}".`)}let f=new Map;for(let t of e.surfaces)f.set(t.id,t.parentSurfaceId);for(let n of e.surfaces){let e=new Set([n.id]),r=n.parentSurfaceId;for(;r;){if(e.has(r)){t.push(`Surface ${n.id} is part of a parent-surface cycle (reached "${r}" again following parentSurfaceId chain).`);break}e.add(r),r=f.get(r)}}return t.length===0?{valid:!0}:{valid:!1,errors:t}},Mn=(e,t)=>{if(t.valid)return[];let n=e.valid?new Set:new Set(e.errors);return t.errors.filter(e=>!n.has(e))},Nn=(e,t)=>{let n={valid:!0},r=Mn(e?Dn(e):n,Dn(t)),i=Mn(e?jn(e):n,jn(t));return[...r,...i]},Pn=e=>({...e,surfaces:e.surfaces.map(Fn)}),Fn=e=>({...e,actions:e.actions.map(In)}),In=e=>{let t=Ln(e);if(t.size===0)return e;let n=new Set(e.emittedEvents.map(String)),r=!1;for(let e of t)n.has(e)||(n.add(e),r=!0);return r?{...e,emittedEvents:[...n].map(e=>un(e))}:e},Ln=e=>{let t=new Set,n=e=>{e.type===`emit_event`&&t.add(String(e.event))};for(let t of e.effects)n(t);for(let t of e.onBlockedEffects??[])n(t);for(let t of e.rules)n(t.effect);return t},Rn=e=>({...e,surfaces:e.surfaces.map(zn)}),zn=e=>({...e,rules:e.rules.map(Vn),invariants:e.invariants.map(Hn),actions:e.actions.map(Bn)}),Bn=e=>({...e,rules:e.rules.map(Vn),invariants:e.invariants.map(Hn),effects:e.effects.map(J),onBlockedEffects:e.onBlockedEffects?.map(J),scenarios:e.scenarios?.map(Un)}),Vn=e=>e.condition===void 0?{...e,effect:J(e.effect)}:{...e,condition:q(e.condition),effect:J(e.effect)},Hn=e=>({...e,condition:q(e.condition)}),q=e=>T(e)?e.kind===`not`?{kind:`not`,condition:q(e.condition)}:{kind:e.kind,conditions:e.conditions.map(q)}:e.right===void 0?e:{...e,right:M(e.right)},J=e=>e.type===`set_state`?{...e,value:M(e.value)}:e,Un=e=>e.expectedAssertions?{...e,expectedAssertions:e.expectedAssertions.map(Wn)}:e,Wn=e=>e.value===void 0?e:{...e,value:M(e.value)},Gn=e=>{let t=new Map;for(let n of e.surfaces)for(let e of n.stateDefinitions){let r=String(e.path);t.set(r,t.has(r)?null:n.id)}let n=new Map;for(let t of e.surfaces)n.set(String(t.id),Kn(t));let r=!1,i=e.surfaces.map(i=>{let a=i.stateDefinitions.map(a=>{let o=String(a.path),s=t.get(o);if(s===null||s!==i.id)return a;let c=[];for(let t of e.surfaces)t.id!==i.id&&n.get(String(t.id))?.has(o)&&c.push(nt(String(t.id)));if(c.length===0)return a;let l=new Set((a.sharedWith??[]).map(String)),u=c.filter(e=>!l.has(String(e)));return u.length===0?a:(r=!0,{...a,sharedWith:[...a.sharedWith??[],...u]})});return a.every((e,t)=>e===i.stateDefinitions[t])?i:{...i,stateDefinitions:a}});return r?{...e,surfaces:i}:e},Kn=e=>{let t=new Set,n=e=>{for(let n of E(e))t.add(String(n.left)),D(n.right)&&Y(n.right,t)},r=e=>{e.type===`set_state`&&(t.add(String(e.path)),D(e.value)&&Y(e.value,t))};for(let t of e.rules)n(t.condition),r(t.effect);for(let t of e.invariants)n(t.condition);for(let i of e.actions){for(let e of i.requiredStates)t.add(String(e));for(let e of i.rules)n(e.condition),r(e.effect);for(let e of i.invariants)n(e.condition);for(let e of i.effects)r(e);for(let e of i.onBlockedEffects??[])r(e)}return t},Y=(e,t)=>{if(D(e))switch(e.kind){case`literal`:case`param`:return;case`state`:t.add(String(e.path));return;case`neg`:case`not`:case`sum`:case`count`:case`sum_pluck`:Y(e.operand,t);return;case`count_where`:Y(e.operand,t),Y(e.equals,t);return;case`switch`:for(let n of e.cases){for(let e of E(n.when))t.add(String(e.left)),D(e.right)&&Y(e.right,t);Y(n.then,t)}Y(e.default,t);return;default:Y(e.left,t),Y(e.right,t)}},qn=e=>Gn(Pn(Rn(e))),Jn=class extends Error{errors;constructor(e,t){super(e),this.errors=t,this.name=`FeatureValidationError`}},Yn=class extends Error{constructor(e){super(`Feature ${e} not found`),this.name=`FeatureNotFoundError`}},Xn=e=>async t=>{let n=await e.repository.get(t.featureId);if(!n)throw new Yn(t.featureId);let r=qn(t.transform(n)),i=Nn(qn(n),r);if(i.length>0)throw new Jn(`The requested change would introduce new validation errors. (Pre-existing issues elsewhere in the feature are not blocking — fix them whenever; they show up in get_spec_gaps / score_feature.)`,i);let a={...r,updatedAt:e.clock()};return await e.repository.save(a),a},Zn=e=>async t=>{let n=Nn(await e.repository.get(t.id),t);if(n.length>0)throw new Jn(`Cannot save: the change would introduce new validation errors.`,n);let r={...t,updatedAt:e.clock()};return await e.repository.save(r),r},Qn=e=>{let t=String(e).trim();if(t.length===0)return``;let n=[];for(let e of t.split(`.`)){let t=e.replace(/([a-z0-9])([A-Z])/g,`$1 $2`).replace(/_+/g,` `).toLowerCase().split(/\s+/).filter(e=>e.length>0);n.push(...t)}if(n.length===0)return t;let r=n[0],i=n.slice(1);return[r.charAt(0).toUpperCase()+r.slice(1),...i].join(` `)},$n=e=>{let t=e.trim();if(t.length===0)return``;if(t.includes(`.`)&&/^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$/.test(t))return t;if(/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(t))return t.charAt(0).toLowerCase()+t.slice(1);let n=t.replace(/[^a-zA-Z0-9\s_]+/g,` `).toLowerCase().split(/[\s_]+/).filter(e=>e.length>0);return n.length===0?``:n.length===1?n[0]:`${n[0]}.${n.slice(1).map((e,t)=>t===0?e:e.charAt(0).toUpperCase()+e.slice(1)).join(``)}`},er={},tr=(e,t)=>{if(typeof e!=`string`||!e.startsWith(`$`))return e;if(e===`$increment`)return{kind:`add`,left:{kind:`state`,path:t},right:{kind:`literal`,value:1}};if(e===`$decrement`)return{kind:`sub`,left:{kind:`state`,path:t},right:{kind:`literal`,value:1}};if(e.startsWith(`$param.`))return{kind:`param`,name:e.slice(7)};if(e.startsWith(`$compute.`)){let t=e.slice(9).match(/^([a-zA-Z_][a-zA-Z0-9_.]*)([+\-*/])([a-zA-Z0-9_.]+)$/);if(t){let e={kind:`state`,path:t[1]},n=Number(t[3]),r=isNaN(n)?{kind:`state`,path:t[3]}:{kind:`literal`,value:n},i={"+":`add`,"-":`sub`,"*":`mul`,"/":`div`}[t[2]];if(i)return{kind:i,left:e,right:r}}}return e},nr=e=>({snapshot:e,applied:[],messages:[],events:[],transition:null,blocked:!1,blockReasons:[]}),rr=e=>{switch(e.type){case`set_state`:return`set ${Qn(e.path)} to ${D(e.value)?`<expr:${e.value.kind}>`:JSON.stringify(e.value)}`;case`show_message`:return`show "${e.message}"`;case`emit_event`:return`emit ${e.event}`;case`block_action`:return`block: ${e.reason}`;case`allow_action`:return`allow`;case`transition_surface`:return`transition to surface ${e.target}`}},ir=(e,t,n=er)=>{let r={effectId:t.id,type:t.type,summary:rr(t)};switch(t.type){case`set_state`:{if(e.blocked)return{...e,applied:[...e.applied,r]};let i=Ot(tr(t.value,t.path),{snapshot:e.snapshot,parameters:n});if(i===void 0)return{...e,applied:[...e.applied,r]};let a=C(e.snapshot,t.path,i);return{...e,snapshot:a,applied:[...e.applied,r]}}case`show_message`:return{...e,applied:[...e.applied,r],messages:[...e.messages,{tone:t.tone??`info`,text:t.message}]};case`emit_event`:return{...e,applied:[...e.applied,r],events:[...e.events,t.event]};case`block_action`:return{...e,applied:[...e.applied,r],blocked:!0,blockReasons:[...e.blockReasons,t.reason],messages:[...e.messages,{tone:`warning`,text:t.reason}]};case`allow_action`:return{...e,applied:[...e.applied,r]};case`transition_surface`:return e.transition===null?{...e,applied:[...e.applied,r],transition:t.target}:{...e,applied:[...e.applied,r]};default:throw Error(`EffectApplier: unknown effect type "${t.type??`<missing>`}". Valid types: set_state, show_message, emit_event, block_action, allow_action, transition_surface.`)}},ar={},or=(e,t,n=ar)=>k(e,{snapshot:t,parameters:n}),sr=(e,t,n={})=>{let r=[];for(let i of e)or(i.condition,t,n)||r.push({invariantId:i.id,invariantName:i.name,message:i.message});return r},cr=(e,t,n)=>{let r=n;for(let n of e){if(!n.bindToStatePath)continue;let e=t[n.name];e!==void 0&&(r=C(r,n.bindToStatePath,e))}return r},lr=/^[^\s@]+@[^\s@]+\.[^\s@]+$/,ur=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,dr=/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,fr=/^(([0-9a-f]{1,4}:){7}[0-9a-f]{1,4}|([0-9a-f]{1,4}:){1,7}:|([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}|([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}|([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}|([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}|([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}|[0-9a-f]{1,4}:((:[0-9a-f]{1,4}){1,6})|:((:[0-9a-f]{1,4}){1,7}|:))$/i,pr=/^[0-9a-f]+$/i,mr=/^[A-Za-z0-9+/]+={0,2}$/,hr=/^[a-z0-9]+(?:-[a-z0-9]+)*$/,gr=/^\+[1-9]\d{1,14}$/,_r=/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i,vr=/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/,yr=/^\d{4}-\d{2}-\d{2}$/,br=/^\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/,xr=/^[A-Za-z0-9]+$/,Sr=/^[A-Za-z]+$/,Cr=/^\S+$/,wr=e=>{if(!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})?$/.test(e))return!1;let t=Date.parse(e);return Number.isFinite(t)},Tr=(e,t)=>{switch(t.type){case`non_empty`:return typeof e==`string`&&e.length===0?t.message??`must not be empty`:null;case`min_length`:return typeof e==`string`&&e.length<t.value?t.message??`must be at least ${t.value} characters`:null;case`max_length`:return typeof e==`string`&&e.length>t.value?t.message??`must be at most ${t.value} characters`:null;case`length`:return typeof e==`string`&&e.length!==t.value?t.message??`must be exactly ${t.value} characters`:null;case`pattern`:if(typeof e==`string`)try{if(!new RegExp(t.value).test(e))return t.message??`must match pattern /${t.value}/`}catch{return`invalid regex pattern in validator: ${t.value}`}return null;case`starts_with`:return typeof e==`string`&&!e.startsWith(t.value)?t.message??`must start with "${t.value}"`:null;case`ends_with`:return typeof e==`string`&&!e.endsWith(t.value)?t.message??`must end with "${t.value}"`:null;case`contains`:return typeof e==`string`&&!e.includes(t.value)?t.message??`must contain "${t.value}"`:null;case`alphanumeric`:return typeof e==`string`&&!xr.test(e)?t.message??`must be alphanumeric (letters and digits only)`:null;case`alphabetic`:return typeof e==`string`&&!Sr.test(e)?t.message??`must contain letters only`:null;case`lowercase`:return typeof e==`string`&&e!==e.toLowerCase()?t.message??`must be lowercase`:null;case`uppercase`:return typeof e==`string`&&e!==e.toUpperCase()?t.message??`must be uppercase`:null;case`no_whitespace`:return typeof e==`string`&&!Cr.test(e)?t.message??`must not contain whitespace`:null;case`email`:return typeof e==`string`&&!lr.test(e)?t.message??`must be a valid email address`:null;case`url`:if(typeof e==`string`)try{new URL(e)}catch{return t.message??`must be a valid URL`}return null;case`uuid`:return typeof e==`string`&&!ur.test(e)?t.message??`must be a valid UUID`:null;case`ipv4`:return typeof e==`string`&&!dr.test(e)?t.message??`must be a valid IPv4 address`:null;case`ipv6`:return typeof e==`string`&&!fr.test(e)?t.message??`must be a valid IPv6 address`:null;case`hex`:return typeof e==`string`&&!pr.test(e)?t.message??`must be a hex string`:null;case`base64`:return typeof e==`string`&&!mr.test(e)?t.message??`must be base64-encoded`:null;case`slug`:return typeof e==`string`&&!hr.test(e)?t.message??`must be a slug (lowercase letters, digits, hyphens)`:null;case`phone_e164`:return typeof e==`string`&&!gr.test(e)?t.message??`must be an E.164 phone number (e.g. +33612345678)`:null;case`color_hex`:return typeof e==`string`&&!_r.test(e)?t.message??`must be a hex color (e.g. #1a2b3c)`:null;case`semver`:return typeof e==`string`&&!vr.test(e)?t.message??`must be a SemVer version (e.g. 1.2.3)`:null;case`json`:if(typeof e==`string`)try{JSON.parse(e)}catch{return t.message??`must be valid JSON`}return null;case`iso_date`:return typeof e==`string`&&(!yr.test(e)||Number.isNaN(Date.parse(`${e}T00:00:00Z`)))?t.message??`must be an ISO date (YYYY-MM-DD)`:null;case`iso_datetime`:return typeof e==`string`&&!wr(e)?t.message??`must be an ISO datetime (e.g. 2026-05-08T12:00:00Z)`:null;case`iso_time`:return typeof e==`string`&&!br.test(e)?t.message??`must be an ISO time (HH:MM[:SS])`:null;case`min`:return typeof e==`number`&&e<t.value?t.message??`must be ≥ ${t.value}`:null;case`max`:return typeof e==`number`&&e>t.value?t.message??`must be ≤ ${t.value}`:null;case`multiple_of`:return typeof e==`number`&&(t.value===0||e%t.value!==0)?t.message??`must be a multiple of ${t.value}`:null;case`integer`:return typeof e==`number`&&!Number.isInteger(e)?t.message??`must be an integer`:null;case`finite`:return typeof e==`number`&&!Number.isFinite(e)?t.message??`must be finite`:null;case`safe_integer`:return typeof e==`number`&&!Number.isSafeInteger(e)?t.message??`must be a safe integer`:null;case`positive`:return typeof e==`number`&&e<=0?t.message??`must be positive`:null;case`negative`:return typeof e==`number`&&e>=0?t.message??`must be negative`:null;case`non_negative`:return typeof e==`number`&&e<0?t.message??`must be ≥ 0`:null;case`non_positive`:return typeof e==`number`&&e>0?t.message??`must be ≤ 0`:null}},Er=(e,t,n)=>{let r=[];for(let i of e){let e=t[i.name];if(e===void 0){i.required&&i.defaultValue===void 0&&r.push({parameterName:i.name,reason:`is required`});continue}if(!xn(e,H(i.type))){r.push({parameterName:i.name,reason:`expected ${i.type}, got ${typeof e}`});continue}let a=i.type===`enum`?cn(i,n):void 0;if(a&&typeof e==`string`&&!a.includes(e)){r.push({parameterName:i.name,reason:`must be one of ${a.join(`, `)}`});continue}if(i.validations)for(let t of i.validations){let n=Tr(e,t);if(n){r.push({parameterName:i.name,reason:n});break}}}return r},Dr=(e,t)=>{let n={...t};for(let t of e)n[t.name]===void 0&&t.defaultValue!==void 0&&(n[t.name]=t.defaultValue);return n},Or=e=>{let t={};for(let n of e)t=C(t,n.path,n.defaultValue);return t},kr=(e,t)=>{let n=e;for(let e of t)S(n,e.path)===void 0&&(n=C(n,e.path,e.defaultValue));return n},Ar=e=>({actionId:e.actionId,status:e.status,parameterErrors:e.parameterErrors,evaluatedRules:e.evaluatedRules,appliedEffects:e.application.applied,messages:e.application.messages,emittedEvents:e.application.events,previousState:e.previousState,nextState:e.application.snapshot,invariantViolations:e.invariantViolations,transition:e.application.transition,...e.cascadedHandlers&&e.cascadedHandlers.length>0?{cascadedHandlers:e.cascadedHandlers}:{}}),jr=8,Mr=(e,t,n,r)=>{let i=n,a=[];for(let n of e){let e=or(n.condition,i.snapshot,r);a.push({ruleId:n.id,category:n.category,conditionHeld:e,origin:t}),e&&(i=ir(i,n.effect,r))}return{application:i,records:a}},Nr=(e,t)=>{let n=[];for(let r of e)for(let e of r.surfaces)for(let i of e.actions)i.triggeredByEvent===t&&n.push({feature:r,surface:e,action:i});return n},Pr=(e,t,n,r,i)=>{if(r>=jr)return{cascaded:[],finalSnapshot:n.nextState};let a=t?[e,...t.filter(t=>t.id!==e.id)]:[e],o=[],s=n.nextState;for(let e of n.emittedEvents)for(let n of Nr(a,e)){if(i.has(n.action.id))continue;i.add(n.action.id);let a=Ir({surface:n.surface,action:n.action,snapshot:s,parameters:{},featureInvariants:n.feature.featureInvariants,feature:n.feature,projectFeatures:t},r+1,i);o.push({triggeredBy:e,depth:r+1,result:a}),a.status===`success`&&(s=a.nextState)}return{cascaded:o,finalSnapshot:s}},Fr=e=>Ir(e,0,new Set),Ir=(e,t,n)=>{let{surface:r,action:i,snapshot:a,parameters:o,featureInvariants:s,feature:c,projectFeatures:l}=e,u=kr(wt(a),c?c.surfaces.flatMap(e=>e.stateDefinitions):r.stateDefinitions),d=Dr(i.parameters,o),f=Er(i.parameters,d,c?.valueSets);if(f.length>0)return Ar({actionId:i.id,status:`blocked`,parameterErrors:f,evaluatedRules:[],application:nr(u),previousState:u,invariantViolations:[]});let p=nr(cr(i.parameters,d,u)),m=Mr(r.rules,`surface`,p,d);p=m.application;let h=[...m.records];if(!p.blocked){let e=Mr(i.rules,`action`,p,d);p=e.application,h.push(...e.records)}if(!p.blocked)for(let e of i.effects)p=ir(p,e,d);else if(i.onBlockedEffects&&i.onBlockedEffects.length>0)for(let e of i.onBlockedEffects)p=ir(p,e,d);let g=sr(i.bypassInvariants?[]:[...s??[],...r.invariants,...i.invariants],p.snapshot,d),_=`success`;p.blocked&&(_=`blocked`),g.length>0&&(_=`blocked`);let v=Ar({actionId:i.id,status:_,parameterErrors:[],evaluatedRules:h,application:p,previousState:u,invariantViolations:g});if(c!==void 0&&_===`success`&&v.emittedEvents.length>0){n.add(i.id);let{cascaded:e,finalSnapshot:r}=Pr(c,l,v,t,n);return e.length===0?v:{...v,nextState:r,cascadedHandlers:e}}return v},Lr=()=>e=>{let t=e.feature.surfaces.find(t=>t.id===e.surfaceId);if(!t)throw Error(`Surface ${e.surfaceId} not found`);let n=t.actions.find(t=>t.id===e.actionId);if(!n)throw Error(`Action ${e.actionId} not found`);return Fr({surface:t,action:n,snapshot:e.snapshot,parameters:e.parameters,featureInvariants:e.feature.featureInvariants,feature:e.feature,...e.projectFeatures?{projectFeatures:e.projectFeatures}:{}})},Rr=()=>e=>R(e),zr=e=>e,Br=e=>async t=>{let n=t.name.trim();if(n.length===0)throw Error(`Project name is required`);let r=t.description?.trim()??``;if(r.length===0)throw Error(`Project description is required`);let i=e.clock(),a=t.customTagType?.trim()??``,o=t.customTag?.trim()??``,s=b(t.tags,{type:a,value:o}),c={id:zr(e.ids()),name:n,description:r,...s?{tags:s}:{},featureIds:[],createdAt:i,updatedAt:i};return await e.repository.save(c),c},Vr=e=>async t=>{await e.repository.delete(t)},Hr=e=>async t=>e.repository.get(t),Ur=e=>async()=>e.repository.list(),Wr=e=>async t=>{if(typeof t.name!=`string`||t.name.trim().length===0)throw Error(`Project name is required`);if(typeof t.description!=`string`||t.description.trim().length===0)throw Error(`Project description is required`);let n=t.customTagType?.trim()??``,r=t.customTag?.trim()??``,i=b(t.tags,{type:n,value:r}),a={...t,tags:i,customTagType:void 0,customTag:void 0,updatedAt:e.clock()};return await e.repository.save(a),a},Gr=e=>async(t,n)=>{let r=await e.repository.get(t);if(!r)throw Error(`Project ${t} not found`);if(r.featureIds.includes(n))return r;let i={...r,featureIds:[...r.featureIds,n],updatedAt:e.clock()};return await e.repository.save(i),i},Kr=e=>async(t,n)=>{let r=await e.repository.get(t);if(!r)throw Error(`Project ${t} not found`);if(!r.featureIds.includes(n))return r;let i={...r,featureIds:r.featureIds.filter(e=>e!==n),updatedAt:e.clock()};return await e.repository.save(i),i},qr=e=>async(t,n,r)=>{let i=await e.repository.get(t);if(!i)throw Error(`Project ${t} not found`);let a=i.featureIds.indexOf(n);if(a<0)throw Error(`Feature ${n} is not attached to project ${t}`);let o=r===`up`?a-1:a+1;if(o<0||o>=i.featureIds.length)return i;let s=i.featureIds.slice();[s[a],s[o]]=[s[o],s[a]];let c={...i,featureIds:s,updatedAt:e.clock()};return await e.repository.save(c),c},Jr=e=>async t=>{let n=await e.projects.get(t);if(!n)return null;let r=[],i=[];for(let t of n.featureIds){let n=await e.features.get(t);n?r.push(n):i.push(t)}let a=[],o=[],s=[],c=[];for(let e of r){for(let t of e.resources)a.push({featureId:e.id,featureName:e.name,id:String(t.id),name:t.name,kind:t.kind,provider:t.provider,sensitivity:t.sensitivity});for(let t of e.entities)o.push({featureId:e.id,featureName:e.name,id:String(t.id),namespace:t.namespace,fieldCount:t.fields.length});for(let t of e.events??[])s.push({featureId:e.id,featureName:e.name,id:String(t.id),name:String(t.name)});for(let t of e.surfaces)for(let n of t.transitions)c.push({featureId:e.id,featureName:e.name,surfaceId:String(t.id),surfaceName:t.name,id:String(n.id),...n.label===void 0?{}:{label:n.label},target:String(n.target)})}return{projectId:n.id,projectName:n.name,featureCount:n.featureIds.length,features:r.map(e=>({id:e.id,name:e.name})),missingFeatureIds:i,resources:a,data:o,events:s,transitions:c}},Yr=e=>async t=>{let n=await e.repository.list();for(let r of n){let n=await e.repository.get(r.id);if(n&&n.featureIds.some(e=>e===t))return n}return null},Xr=e=>e,Zr=e=>Math.max(0,Math.min(100,Math.round(e))),Qr=e=>!e||e.maturity===void 0&&e.implementation===void 0&&!e.report,X=e=>{switch(e.kind){case`feature`:return`feature:${String(e.featureId)}`;case`surface`:return`surface:${String(e.featureId)}:${String(e.surfaceId)}`;case`action`:return`action:${String(e.featureId)}:${String(e.actionId)}`}},Z=(e,t)=>e.findIndex(e=>e.id===t),$r=(e,t)=>e.findIndex(e=>X(e)===t),ei=(e,t)=>$r(e,X(t))>=0?e:[...e,t],ti=(e,t)=>{let n=Z(e,t);return n<0?e:[...e.slice(0,n),...e.slice(n+1)]},ni=(e,t,n)=>{let r=Z(e,t);if(r<0)return e;let i=Math.max(0,Math.min(e.length-1,n));if(i===r)return e;let a=e.slice(),[o]=a.splice(r,1);return a.splice(i,0,o),a},ri=(e,t,n)=>{let r=Z(e,t);return r<0?e:ni(e,t,n===`up`?r-1:r+1)},ii=(e,t)=>$r(e,t)>=0,ai=(e,t,n)=>{let r=Z(e,t);if(r<0)return e;let{target:i,...a}=e[r],o=Qr(n)?a:{...a,target:n};return[...e.slice(0,r),o,...e.slice(r+1)]},Q=class extends Error{constructor(e){super(`Project ${e} not found`),this.name=`ProjectNotFoundForQueueError`}},oi=class extends Error{constructor(e,t){super(`Feature ${e} is not attached to project ${t}`),this.name=`FeatureNotInProjectError`}},si=class extends Error{constructor(e,t){super(`Action ${e} is not part of feature ${t}`),this.name=`ActionNotInFeatureError`}},ci=class extends Error{constructor(e,t){super(`Surface ${e} is not part of feature ${t}`),this.name=`SurfaceNotInFeatureError`}},li=e=>async(t,n)=>{let r=await e.projects.get(t);if(!r)throw new Q(t);if(!r.featureIds.includes(n.featureId))throw new oi(n.featureId,t);if(n.kind===`surface`||n.kind===`action`){let r=await e.features.get(n.featureId);if(!r)throw new oi(n.featureId,t);if(n.kind===`surface`){if(!r.surfaces.some(e=>e.id===n.surfaceId))throw new ci(n.surfaceId,n.featureId)}else if(!r.surfaces.some(e=>e.actions.some(e=>e.id===n.actionId)))throw new si(n.actionId,n.featureId)}let i=r.implementationQueue??[],a=ui(n,e),o=X(a);if(ii(i,o))return{project:r,item:i.find(e=>X(e)===o),alreadyQueued:!0};let s={...r,implementationQueue:ei(i,a),updatedAt:e.clock()};return await e.projects.save(s),{project:s,item:a,alreadyQueued:!1}},ui=(e,t)=>{let n=Xr(t.ids()),r=t.clock(),i=e.note?{note:e.note}:{},a=e.target?{target:e.target}:{};switch(e.kind){case`feature`:return{id:n,kind:`feature`,featureId:e.featureId,addedAt:r,...i,...a};case`surface`:return{id:n,kind:`surface`,featureId:e.featureId,surfaceId:e.surfaceId,addedAt:r,...i,...a};case`action`:return{id:n,kind:`action`,featureId:e.featureId,actionId:e.actionId,addedAt:r,...i,...a}}},di=e=>async(t,n)=>{let r=await e.projects.get(t);if(!r)throw new Q(t);let i=r.implementationQueue??[],a=ti(i,n);if(a===i)return r;let o={...r,implementationQueue:a,updatedAt:e.clock()};return await e.projects.save(o),o},fi=e=>async(t,n,r)=>{let i=await e.projects.get(t);if(!i)throw new Q(t);let a=i.implementationQueue??[],o=r.kind===`to`?ni(a,n,r.targetIndex):ri(a,n,r.direction);if(o===a)return i;let s={...i,implementationQueue:o,updatedAt:e.clock()};return await e.projects.save(s),s},pi=e=>async t=>{let n=await e.projects.get(t);if(!n)throw new Q(t);let r=n.implementationQueue??[];if(r.length===0)return[];let i=new Map,a=async t=>{let n=String(t);if(i.has(n))return i.get(n);let r=await e.features.get(t);return i.set(n,r),r},o=[];for(let e of r){let t=await a(e.featureId);if(!t){o.push({item:e,featureName:`(missing feature ${e.featureId})`,orphaned:!0});continue}if(e.kind===`feature`){o.push({item:e,featureName:t.name,orphaned:!1});continue}if(e.kind===`surface`){let n=t.surfaces.find(t=>t.id===e.surfaceId);if(!n){o.push({item:e,featureName:t.name,surfaceName:`(missing surface ${e.surfaceId})`,orphaned:!0});continue}o.push({item:e,featureName:t.name,surfaceName:n.name,orphaned:!1});continue}let n=t.surfaces.flatMap(e=>e.actions).find(t=>t.id===e.actionId);if(!n){o.push({item:e,featureName:t.name,actionName:`(missing action ${e.actionId})`,orphaned:!0});continue}o.push({item:e,featureName:t.name,actionName:n.name,orphaned:!1})}return o},mi=e=>async(t,n,r)=>{let i=await e.projects.get(t);if(!i)throw new Q(t);let a=i.implementationQueue??[],o=ai(a,n,r);if(o===a)return i;let s={...i,implementationQueue:o,updatedAt:e.clock()};return await e.projects.save(s),s},hi=e=>e,gi=e=>async t=>{let n=t.name.trim();if(n.length===0)throw Error(`Domain name is required`);let r=e.clock(),i={id:hi(e.ids()),name:n,description:t.description?.trim()||void 0,projectIds:[],createdAt:r,updatedAt:r};return await e.repository.save(i),i},_i=e=>t=>e.repository.delete(t),vi=e=>t=>e.repository.get(t),yi=e=>()=>e.repository.list(),bi=e=>async t=>{let n={...t,updatedAt:e.clock()};return await e.repository.save(n),n},xi=e=>async(t,n)=>{let r=await e.repository.get(t);if(!r)throw Error(`Domain ${t} not found`);if(r.projectIds.includes(n))return r;let i={...r,projectIds:[...r.projectIds,n],updatedAt:e.clock()};return await e.repository.save(i),i},Si=e=>async(t,n)=>{let r=await e.repository.get(t);if(!r)throw Error(`Domain ${t} not found`);let i={...r,projectIds:r.projectIds.filter(e=>e!==n),updatedAt:e.clock()};return await e.repository.save(i),i},Ci=e=>async()=>e.repository.get(),wi=e=>async t=>{if(t.color!==null&&!He(t.color))throw Error(`Invalid tag color "${String(t.color)}". Expected a 6-digit hex like "#3b82f6".`);let n=await e.repository.get(),r=t.color===null?qe(n,t.type):Ke(n,t.type,t.color);return r===n?n:(await e.repository.save(r),r)},Ti=e=>{let t=e.clock??$e,n=e.ids??et,r=e.repository,i=e.projectRepository,a=e.domainRepository,o=e.statusRepository,s=e.tagPaletteRepository,c=e.tagOperations;return{repository:r,projectRepository:i,domainRepository:a,statusRepository:o,tagPaletteRepository:s,tagOperations:c,clock:t,ids:n,useCases:{createFeature:gt({repository:r,clock:t,ids:n}),saveFeature:Zn({repository:r,clock:t}),mutateFeature:Xn({repository:r,clock:t}),deleteFeature:_t({repository:r}),listFeatures:an({repository:r}),listFeaturesWithMaturity:on({repository:r,statusRepository:o}),getFeature:vt({repository:r}),getFeatureCard:rn({repository:r,statusRepository:o}),loadSamples:sn({repository:r,projectRepository:i,samples:e.samples,projectSamples:e.projectSamples}),simulateAction:Lr(),scoreFeature:Rr(),createProject:Br({repository:i,clock:t,ids:n}),saveProject:Wr({repository:i,clock:t}),deleteProject:Vr({repository:i}),getProject:Hr({repository:i}),listProjects:Ur({repository:i}),addFeatureToProject:Gr({repository:i,clock:t}),removeFeatureFromProject:Kr({repository:i,clock:t}),moveFeatureInProject:qr({repository:i,clock:t}),getProjectAggregate:Jr({projects:i,features:r}),findProjectContainingFeature:Yr({repository:i}),createDomain:gi({repository:a,clock:t,ids:n}),saveDomain:bi({repository:a,clock:t}),deleteDomain:_i({repository:a}),getDomain:vi({repository:a}),listDomains:yi({repository:a}),addProjectToDomain:xi({repository:a,clock:t}),removeProjectFromDomain:Si({repository:a,clock:t}),enqueueQueueItem:li({projects:i,features:r,clock:t,ids:n}),dequeueQueueItem:di({projects:i,clock:t}),moveQueueItem:fi({projects:i,clock:t}),listQueue:pi({projects:i,features:r}),setQueueItemTarget:mi({projects:i,clock:t}),getTagPalette:Ci({repository:s}),setTagTypeColor:wi({repository:s}),renameTag:e=>c.renameTag(e)}}},$=null,Ei=async()=>{if($)return $;let e=Ti({repository:new v,projectRepository:new Ne,domainRepository:new Ie,statusRepository:new Re,tagPaletteRepository:new Be,tagOperations:new Qe,samples:[...ae,...ke],projectSamples:Ae});return $=e,e};export{We as $,T as A,ft as B,un as C,Xt as D,R as E,C as F,ut as G,tt as H,yt as I,mt as J,dt as K,rt as L,St as M,xt as N,D as O,S as P,et as Q,ot as R,H as S,nn as T,at as U,ht as V,ct as W,nt as X,st as Y,lt as Z,Pn as _,i as _t,zr as a,ne as at,pn as b,Fr as c,ie as ct,or as d,h as dt,Xe as et,Qn as f,d as ft,Rn as g,a as gt,Gn as h,c as ht,X as i,ee as it,w as j,E as k,Or as l,x as lt,Jn as m,s as mt,Zr as n,Ue as nt,Rr as o,b as ot,$n as p,o as pt,it as q,Qr as r,Ke as rt,Lr as s,te as st,Ei as t,Ye as tt,Dr as u,re as ut,yn as v,l as vt,V as w,dn as x,fn as y,pt as z};
|
|
Binary file
|
|
Binary file
|