@transcend-io/cli 12.1.1 → 12.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{app-BAx2hvcN.mjs → app-CnGAV_Tf.mjs} +16 -16
- package/dist/{app-BAx2hvcN.mjs.map → app-CnGAV_Tf.mjs.map} +1 -1
- package/dist/{approvePrivacyRequests-B-4o7QMG.mjs → approvePrivacyRequests-fmkUKYAm.mjs} +2 -2
- package/dist/{approvePrivacyRequests-B-4o7QMG.mjs.map → approvePrivacyRequests-fmkUKYAm.mjs.map} +1 -1
- package/dist/bin/bash-complete.mjs +1 -1
- package/dist/bin/cli.mjs +1 -1
- package/dist/bin/deprecated-command.mjs +1 -1
- package/dist/{buildExamples-kR5_7dkU.mjs → buildExamples-DB_puA14.mjs} +2 -2
- package/dist/{buildExamples-kR5_7dkU.mjs.map → buildExamples-DB_puA14.mjs.map} +1 -1
- package/dist/{buildXdiSyncEndpoint-BSYwJ2vc.mjs → buildXdiSyncEndpoint-CwABTp_y.mjs} +2 -2
- package/dist/{buildXdiSyncEndpoint-BSYwJ2vc.mjs.map → buildXdiSyncEndpoint-CwABTp_y.mjs.map} +1 -1
- package/dist/{bulkRestartRequests-DhOfMz0S.mjs → bulkRestartRequests-3OpVNSrk.mjs} +2 -2
- package/dist/{bulkRestartRequests-DhOfMz0S.mjs.map → bulkRestartRequests-3OpVNSrk.mjs.map} +1 -1
- package/dist/{bulkRetryEnrichers-Cj94ftnY.mjs → bulkRetryEnrichers-CdcXcHsx.mjs} +2 -2
- package/dist/{bulkRetryEnrichers-Cj94ftnY.mjs.map → bulkRetryEnrichers-CdcXcHsx.mjs.map} +1 -1
- package/dist/{cancelPrivacyRequests-DgAhfcmg.mjs → cancelPrivacyRequests-Dcl2KZfB.mjs} +2 -2
- package/dist/{cancelPrivacyRequests-DgAhfcmg.mjs.map → cancelPrivacyRequests-Dcl2KZfB.mjs.map} +1 -1
- package/dist/{command-gwG8sQwM.mjs → command-BcxEDvsy.mjs} +2 -2
- package/dist/{command-gwG8sQwM.mjs.map → command-BcxEDvsy.mjs.map} +1 -1
- package/dist/{constants-BCmVqlrM.mjs → constants-boymUaFC.mjs} +2 -2
- package/dist/{constants-BCmVqlrM.mjs.map → constants-boymUaFC.mjs.map} +1 -1
- package/dist/{createPoolingCommandUi-ByyTeJx5.mjs → createPoolingCommandUi-D7gyxeTT.mjs} +2 -2
- package/dist/{createPoolingCommandUi-ByyTeJx5.mjs.map → createPoolingCommandUi-D7gyxeTT.mjs.map} +1 -1
- package/dist/{downloadPrivacyRequestFiles-yiE6NzXz.mjs → downloadPrivacyRequestFiles-930ENWDi.mjs} +2 -2
- package/dist/{downloadPrivacyRequestFiles-yiE6NzXz.mjs.map → downloadPrivacyRequestFiles-930ENWDi.mjs.map} +1 -1
- package/dist/{generateCrossAccountApiKeys-CoSsZwSz.mjs → generateCrossAccountApiKeys-B_p8gQ2C.mjs} +2 -2
- package/dist/{generateCrossAccountApiKeys-CoSsZwSz.mjs.map → generateCrossAccountApiKeys-B_p8gQ2C.mjs.map} +1 -1
- package/dist/{impl-DdUe8ClA.mjs → impl-2VeGCbJq.mjs} +2 -2
- package/dist/{impl-DdUe8ClA.mjs.map → impl-2VeGCbJq.mjs.map} +1 -1
- package/dist/{impl-tYHONQMu.mjs → impl-2YeOxW2m.mjs} +2 -2
- package/dist/{impl-tYHONQMu.mjs.map → impl-2YeOxW2m.mjs.map} +1 -1
- package/dist/{impl-BCP4X_zk.mjs → impl-5Pko18bX.mjs} +2 -2
- package/dist/{impl-BCP4X_zk.mjs.map → impl-5Pko18bX.mjs.map} +1 -1
- package/dist/{impl-pA1b-G6B.mjs → impl-7YqcGwra.mjs} +2 -2
- package/dist/{impl-pA1b-G6B.mjs.map → impl-7YqcGwra.mjs.map} +1 -1
- package/dist/{impl-DSPxdc-T.mjs → impl-B5-UipRP.mjs} +2 -2
- package/dist/{impl-DSPxdc-T.mjs.map → impl-B5-UipRP.mjs.map} +1 -1
- package/dist/{impl-DE_cRfz52.mjs → impl-B8PyNdje2.mjs} +2 -2
- package/dist/{impl-DE_cRfz52.mjs.map → impl-B8PyNdje2.mjs.map} +1 -1
- package/dist/{impl-C1vn0Y7f.mjs → impl-BIYT-9BY.mjs} +2 -2
- package/dist/{impl-C1vn0Y7f.mjs.map → impl-BIYT-9BY.mjs.map} +1 -1
- package/dist/{impl-CyDdvH5a.mjs → impl-BP110k4H.mjs} +2 -2
- package/dist/{impl-CyDdvH5a.mjs.map → impl-BP110k4H.mjs.map} +1 -1
- package/dist/{impl-C0R2azuF.mjs → impl-BPdvRtQ9.mjs} +2 -2
- package/dist/{impl-C0R2azuF.mjs.map → impl-BPdvRtQ9.mjs.map} +1 -1
- package/dist/{impl-D0cf-epj.mjs → impl-BUJbKCd4.mjs} +2 -2
- package/dist/{impl-D0cf-epj.mjs.map → impl-BUJbKCd4.mjs.map} +1 -1
- package/dist/{impl-BKKBmXv4.mjs → impl-BbzX8auO.mjs} +2 -2
- package/dist/{impl-BKKBmXv4.mjs.map → impl-BbzX8auO.mjs.map} +1 -1
- package/dist/{impl-4WoLxjdU.mjs → impl-Bs4T0u8t.mjs} +2 -2
- package/dist/{impl-4WoLxjdU.mjs.map → impl-Bs4T0u8t.mjs.map} +1 -1
- package/dist/{impl-BRCAL4RM.mjs → impl-BtIse0WU.mjs} +2 -2
- package/dist/{impl-BRCAL4RM.mjs.map → impl-BtIse0WU.mjs.map} +1 -1
- package/dist/{impl-CuDZY6y7.mjs → impl-C4AjYGSc.mjs} +2 -2
- package/dist/{impl-CuDZY6y7.mjs.map → impl-C4AjYGSc.mjs.map} +1 -1
- package/dist/{impl-DqnzZv43.mjs → impl-CFyD51rZ.mjs} +2 -2
- package/dist/{impl-DqnzZv43.mjs.map → impl-CFyD51rZ.mjs.map} +1 -1
- package/dist/{impl-F2lRJEqw.mjs → impl-CKeiISid.mjs} +2 -2
- package/dist/{impl-F2lRJEqw.mjs.map → impl-CKeiISid.mjs.map} +1 -1
- package/dist/{impl-DCF3CF3O.mjs → impl-CKlQglut.mjs} +2 -2
- package/dist/{impl-DCF3CF3O.mjs.map → impl-CKlQglut.mjs.map} +1 -1
- package/dist/{impl-DNRA5i0w.mjs → impl-CPyd-ahE.mjs} +2 -2
- package/dist/{impl-DNRA5i0w.mjs.map → impl-CPyd-ahE.mjs.map} +1 -1
- package/dist/{impl-CxpGBeJ0.mjs → impl-CtMcCdUA.mjs} +2 -2
- package/dist/{impl-CxpGBeJ0.mjs.map → impl-CtMcCdUA.mjs.map} +1 -1
- package/dist/{impl-BaGN4J-w.mjs → impl-CwWY8MCP.mjs} +2 -2
- package/dist/{impl-BaGN4J-w.mjs.map → impl-CwWY8MCP.mjs.map} +1 -1
- package/dist/{impl-DrlfmH8g.mjs → impl-CwjhuIx0.mjs} +2 -2
- package/dist/{impl-DrlfmH8g.mjs.map → impl-CwjhuIx0.mjs.map} +1 -1
- package/dist/{impl-u1nIo2-E.mjs → impl-Cx97QWq8.mjs} +2 -2
- package/dist/{impl-u1nIo2-E.mjs.map → impl-Cx97QWq8.mjs.map} +1 -1
- package/dist/{impl-BJnxahnW.mjs → impl-D-Cz399O.mjs} +2 -2
- package/dist/{impl-BJnxahnW.mjs.map → impl-D-Cz399O.mjs.map} +1 -1
- package/dist/{impl-BM2SWSKF.mjs → impl-D3LHj5lS.mjs} +2 -2
- package/dist/{impl-BM2SWSKF.mjs.map → impl-D3LHj5lS.mjs.map} +1 -1
- package/dist/{impl-Dl8xSx8K.mjs → impl-D9kZ4naw.mjs} +2 -2
- package/dist/{impl-Dl8xSx8K.mjs.map → impl-D9kZ4naw.mjs.map} +1 -1
- package/dist/{impl-f59q7WnK.mjs → impl-DBMuhicF.mjs} +2 -2
- package/dist/{impl-f59q7WnK.mjs.map → impl-DBMuhicF.mjs.map} +1 -1
- package/dist/{impl-LDY2dgtc.mjs → impl-DDsOdK7O.mjs} +2 -2
- package/dist/{impl-LDY2dgtc.mjs.map → impl-DDsOdK7O.mjs.map} +1 -1
- package/dist/{impl-BFSZEQ2s.mjs → impl-DFhAp1Ky.mjs} +2 -2
- package/dist/{impl-BFSZEQ2s.mjs.map → impl-DFhAp1Ky.mjs.map} +1 -1
- package/dist/{impl-C1hh_5On.mjs → impl-DGm6q0bm.mjs} +2 -2
- package/dist/{impl-C1hh_5On.mjs.map → impl-DGm6q0bm.mjs.map} +1 -1
- package/dist/{impl-BMX6B40F.mjs → impl-DPb4YZxb.mjs} +2 -2
- package/dist/{impl-BMX6B40F.mjs.map → impl-DPb4YZxb.mjs.map} +1 -1
- package/dist/{impl-DbddBTRj2.mjs → impl-DRmJog0S2.mjs} +2 -2
- package/dist/{impl-DbddBTRj2.mjs.map → impl-DRmJog0S2.mjs.map} +1 -1
- package/dist/{impl-Bf2Qli6c2.mjs → impl-DT4VbZ-Q2.mjs} +2 -2
- package/dist/{impl-Bf2Qli6c2.mjs.map → impl-DT4VbZ-Q2.mjs.map} +1 -1
- package/dist/{impl-D8YS5Bge.mjs → impl-Ds6ROQi7.mjs} +2 -2
- package/dist/{impl-D8YS5Bge.mjs.map → impl-Ds6ROQi7.mjs.map} +1 -1
- package/dist/{impl-ClhMRgQZ.mjs → impl-P1hltmA6.mjs} +2 -2
- package/dist/{impl-ClhMRgQZ.mjs.map → impl-P1hltmA6.mjs.map} +1 -1
- package/dist/{impl-BrdCuU0I.mjs → impl-XztvsE6N.mjs} +2 -2
- package/dist/{impl-BrdCuU0I.mjs.map → impl-XztvsE6N.mjs.map} +1 -1
- package/dist/{impl-C54oUOeM2.mjs → impl-qTeP2Voo2.mjs} +2 -2
- package/dist/{impl-C54oUOeM2.mjs.map → impl-qTeP2Voo2.mjs.map} +1 -1
- package/dist/{impl-OXGlZyUo.mjs → impl-xR4iG_u5.mjs} +2 -2
- package/dist/{impl-OXGlZyUo.mjs.map → impl-xR4iG_u5.mjs.map} +1 -1
- package/dist/{impl-CKg5Xu-r.mjs → impl-yJETNVcT.mjs} +2 -2
- package/dist/{impl-CKg5Xu-r.mjs.map → impl-yJETNVcT.mjs.map} +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{markRequestDataSiloIdsCompleted-B-Dgme2r.mjs → markRequestDataSiloIdsCompleted-CU0ba_mE.mjs} +2 -2
- package/dist/{markRequestDataSiloIdsCompleted-B-Dgme2r.mjs.map → markRequestDataSiloIdsCompleted-CU0ba_mE.mjs.map} +1 -1
- package/dist/{markSilentPrivacyRequests-CPU4Q2lF.mjs → markSilentPrivacyRequests-Bf_4pCaq.mjs} +2 -2
- package/dist/{markSilentPrivacyRequests-CPU4Q2lF.mjs.map → markSilentPrivacyRequests-Bf_4pCaq.mjs.map} +1 -1
- package/dist/{notifyPrivacyRequestsAdditionalTime-BWsm5Y5K.mjs → notifyPrivacyRequestsAdditionalTime-rnUXdvBR.mjs} +2 -2
- package/dist/{notifyPrivacyRequestsAdditionalTime-BWsm5Y5K.mjs.map → notifyPrivacyRequestsAdditionalTime-rnUXdvBR.mjs.map} +1 -1
- package/dist/{policy-bundle-manifest-BDey6EBs.mjs → policy-bundle-manifest-B1Herd_n.mjs} +2 -2
- package/dist/{policy-bundle-manifest-BDey6EBs.mjs.map → policy-bundle-manifest-B1Herd_n.mjs.map} +1 -1
- package/dist/{policy-project-discovery-DV5PnFJq.mjs → policy-project-discovery-oc49b2gr.mjs} +2 -2
- package/dist/{policy-project-discovery-DV5PnFJq.mjs.map → policy-project-discovery-oc49b2gr.mjs.map} +1 -1
- package/dist/{policy-scaffold-config-DtRELUZx.mjs → policy-scaffold-config-CpXR9KgF.mjs} +2 -2
- package/dist/{policy-scaffold-config-DtRELUZx.mjs.map → policy-scaffold-config-CpXR9KgF.mjs.map} +1 -1
- package/dist/{pullChunkedCustomSiloOutstandingIdentifiers-BaHp-jxs.mjs → pullChunkedCustomSiloOutstandingIdentifiers-BcBj9prQ.mjs} +2 -2
- package/dist/{pullChunkedCustomSiloOutstandingIdentifiers-BaHp-jxs.mjs.map → pullChunkedCustomSiloOutstandingIdentifiers-BcBj9prQ.mjs.map} +1 -1
- package/dist/{pullManualEnrichmentIdentifiersToCsv-CiEFIvl_.mjs → pullManualEnrichmentIdentifiersToCsv-BSUXUp8v.mjs} +2 -2
- package/dist/{pullManualEnrichmentIdentifiersToCsv-CiEFIvl_.mjs.map → pullManualEnrichmentIdentifiersToCsv-BSUXUp8v.mjs.map} +1 -1
- package/dist/{pushCronIdentifiersFromCsv-DeS78zyp.mjs → pushCronIdentifiersFromCsv-D-l3erLO.mjs} +2 -2
- package/dist/{pushCronIdentifiersFromCsv-DeS78zyp.mjs.map → pushCronIdentifiersFromCsv-D-l3erLO.mjs.map} +1 -1
- package/dist/{pushManualEnrichmentIdentifiersFromCsv-B3iVwuvs.mjs → pushManualEnrichmentIdentifiersFromCsv-CqLIw5MV.mjs} +2 -2
- package/dist/{pushManualEnrichmentIdentifiersFromCsv-B3iVwuvs.mjs.map → pushManualEnrichmentIdentifiersFromCsv-CqLIw5MV.mjs.map} +1 -1
- package/dist/{removeUnverifiedRequestIdentifiers-qS5A_eRZ.mjs → removeUnverifiedRequestIdentifiers-B-Az2HJ8.mjs} +2 -2
- package/dist/{removeUnverifiedRequestIdentifiers-qS5A_eRZ.mjs.map → removeUnverifiedRequestIdentifiers-B-Az2HJ8.mjs.map} +1 -1
- package/dist/{retryRequestDataSilos-Cxaka-kA.mjs → retryRequestDataSilos-D70pC9JF.mjs} +2 -2
- package/dist/{retryRequestDataSilos-Cxaka-kA.mjs.map → retryRequestDataSilos-D70pC9JF.mjs.map} +1 -1
- package/dist/{skipPreflightJobs-FdIm_Imj.mjs → skipPreflightJobs-B0YG7mhP.mjs} +2 -2
- package/dist/{skipPreflightJobs-FdIm_Imj.mjs.map → skipPreflightJobs-B0YG7mhP.mjs.map} +1 -1
- package/dist/{skipRequestDataSilos-MTbj-OfW.mjs → skipRequestDataSilos-DV5Dy5Dw.mjs} +2 -2
- package/dist/{skipRequestDataSilos-MTbj-OfW.mjs.map → skipRequestDataSilos-DV5Dy5Dw.mjs.map} +1 -1
- package/dist/{streamPrivacyRequestsToCsv-BVzYSXNY.mjs → streamPrivacyRequestsToCsv-Qm2r7Iy2.mjs} +2 -2
- package/dist/{streamPrivacyRequestsToCsv-BVzYSXNY.mjs.map → streamPrivacyRequestsToCsv-Qm2r7Iy2.mjs.map} +1 -1
- package/dist/{updateConsentManagerVersionToLatest-Udi7Iazb.mjs → updateConsentManagerVersionToLatest-cryDInky.mjs} +2 -2
- package/dist/{updateConsentManagerVersionToLatest-Udi7Iazb.mjs.map → updateConsentManagerVersionToLatest-cryDInky.mjs.map} +1 -1
- package/dist/{uploadConsents-w7FE65Zg.mjs → uploadConsents-Din2Qdws.mjs} +2 -2
- package/dist/{uploadConsents-w7FE65Zg.mjs.map → uploadConsents-Din2Qdws.mjs.map} +1 -1
- package/dist/{uploadPrivacyRequestsFromCsv-B__QsguT.mjs → uploadPrivacyRequestsFromCsv-DHYbgV8h.mjs} +2 -2
- package/dist/{uploadPrivacyRequestsFromCsv-B__QsguT.mjs.map → uploadPrivacyRequestsFromCsv-DHYbgV8h.mjs.map} +1 -1
- package/package.json +5 -5
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{t as e}from"./pushCronIdentifiersFromCsv-
|
|
2
|
-
//# sourceMappingURL=impl-
|
|
1
|
+
import{t as e}from"./pushCronIdentifiersFromCsv-D-l3erLO.mjs";import{t}from"./done-input-validation-LiTaCEzF.mjs";async function n({file:n,transcendUrl:r,auth:i,sombraAuth:a,dataSiloId:o}){t(this.process),await e({file:n,transcendUrl:r,auth:i,sombraAuth:a,dataSiloId:o})}export{n as markIdentifiersCompleted};
|
|
2
|
+
//# sourceMappingURL=impl-yJETNVcT.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"impl-
|
|
1
|
+
{"version":3,"file":"impl-yJETNVcT.mjs","names":[],"sources":["../src/commands/request/cron/mark-identifiers-completed/impl.ts"],"sourcesContent":["import type { LocalContext } from '../../../../context.js';\nimport { doneInputValidation } from '../../../../lib/cli/done-input-validation.js';\nimport { pushCronIdentifiersFromCsv } from '../../../../lib/cron/index.js';\n\nexport interface MarkIdentifiersCompletedCommandFlags {\n file: string;\n transcendUrl: string;\n auth: string;\n sombraAuth?: string;\n dataSiloId: string;\n}\n\nexport async function markIdentifiersCompleted(\n this: LocalContext,\n { file, transcendUrl, auth, sombraAuth, dataSiloId }: MarkIdentifiersCompletedCommandFlags,\n): Promise<void> {\n doneInputValidation(this.process);\n\n await pushCronIdentifiersFromCsv({\n file,\n transcendUrl,\n auth,\n sombraAuth,\n dataSiloId,\n });\n}\n"],"mappings":"kHAYA,eAAsB,EAEpB,CAAE,OAAM,eAAc,OAAM,aAAY,cACzB,CACf,EAAoB,KAAK,QAAQ,CAEjC,MAAM,EAA2B,CAC/B,OACA,eACA,OACA,aACA,aACD,CAAC"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{i as e,n as t,r as n,t as r}from"./enums-OiLcZ26O.mjs";import{a as i,c as a,d as o,f as s,g as c,h as l,i as u,l as d,m as f,n as p,o as m,p as h,r as g,s as _,t as v,u as y}from"./constants-
|
|
1
|
+
import{i as e,n as t,r as n,t as r}from"./enums-OiLcZ26O.mjs";import{a as i,c as a,d as o,f as s,g as c,h as l,i as u,l as d,m as f,n as p,o as m,p as h,r as g,s as _,t as v,u as y}from"./constants-boymUaFC.mjs";import{t as b}from"./logger-CPu6zbyQ.mjs";import{$ as x,A as S,B as ee,C as te,D as C,E as ne,F as re,G as ie,H as ae,I as oe,J as se,K as ce,L as le,M as ue,N as w,O as de,P as fe,Q as T,R as E,S as D,T as O,U as k,V as A,W as j,X as M,Y as N,Z as P,_ as F,a as I,at as L,b as R,c as z,ct as B,d as V,dt as pe,et as me,f as he,ft as ge,g as _e,gt as ve,h as ye,ht as be,i as xe,it as Se,j as Ce,k as we,l as Te,lt as Ee,m as De,mt as Oe,n as ke,nt as Ae,o as je,ot as Me,p as Ne,pt as Pe,q as Fe,r as Ie,rt as Le,s as Re,st as ze,t as Be,tt as Ve,u as He,ut as Ue,v as We,w as Ge,x as Ke,y as qe,z as Je}from"./codecs-CfcaFoZR.mjs";import{a as Ye,i as Xe,n as Ze,o as Qe,r as $e,s as et,t as tt}from"./readTranscendYaml-BfqQbPiT.mjs";import{i as nt,n as rt,r as it,t as at}from"./mergeTranscendInputs-B7WjxYMN.mjs";import{a as ot,c as st,i as ct,l as lt,n as ut,o as dt,r as ft,s as pt,t as mt,u as ht}from"./constants-DlH2vWm_.mjs";import{n as gt,t as _t}from"./fuzzyMatchColumns-D18n9Beu.mjs";import{a as vt,c as yt,d as bt,f as xt,i as St,l as Ct,n as wt,o as Tt,r as Et,s as Dt,t as Ot,u as kt}from"./uploadPrivacyRequestsFromCsv-DHYbgV8h.mjs";import{n as At,t as jt}from"./parseAttributesFromString-C5hJMRvt.mjs";import{n as H,t as Mt}from"./readCsv-DNcJ_UfU.mjs";import{n as Nt,t as Pt}from"./pullUnstructuredSubDataPointRecommendations-Ci4AOxyH.mjs";import{a as Ft,i as It,n as Lt,o as Rt,r as zt,t as Bt}from"./request-BpKyN9Xm.mjs";import{i as Vt,n as Ht,r as Ut,t as Wt}from"./pullTranscendConfiguration-CDUDqxpm.mjs";import{i as Gt,n as Kt,r as U,t as qt}from"./fetchAllRequests-BI0tFl_a.mjs";import{a as Jt,i as Yt,n as Xt,r as Zt,t as Qt}from"./pullChunkedCustomSiloOutstandingIdentifiers-BcBj9prQ.mjs";import{n as $t,r as en,t as tn}from"./syncCodePackages-DJnrcX5l.mjs";import{t as nn}from"./markSilentPrivacyRequests-Bf_4pCaq.mjs";import{a as rn,i as an,n as on,o as sn,r as cn,t as ln}from"./downloadPrivacyRequestFiles-930ENWDi.mjs";import{n as un,r as dn,t as fn}from"./extractClientError-BQgmnp3e.mjs";import{t as pn}from"./approvePrivacyRequests-fmkUKYAm.mjs";import{t as mn}from"./notifyPrivacyRequestsAdditionalTime-rnUXdvBR.mjs";import{t as hn}from"./cancelPrivacyRequests-Dcl2KZfB.mjs";import{n as gn,t as _n}from"./bulkRestartRequests-3OpVNSrk.mjs";import{t as vn}from"./skipPreflightJobs-B0YG7mhP.mjs";import{t as yn}from"./bulkRetryEnrichers-CdcXcHsx.mjs";import{t as bn}from"./retryRequestDataSilos-D70pC9JF.mjs";import{n as W,r as G,t as xn}from"./streamPrivacyRequestsToCsv-Qm2r7Iy2.mjs";import{n as Sn,r as Cn,t as wn}from"./inquirer-jzIQWAvq.mjs";import{t as Tn}from"./parseVariablesFromString-6TYW65LF.mjs";import{t as En}from"./collectParquetFilesOrExit-CbNQ9CJc.mjs";import{t as Dn}from"./collectCsvFilesOrExit-BindZ0dF.mjs";import{t as On}from"./parquetToCsvOneFile-DtbCFm08.mjs";import{a as kn,i as An,n as jn,o as Mn,r as Nn,s as Pn,t as Fn}from"./writeCsv-CNZrq3zU.mjs";import{t as In}from"./skipRequestDataSilos-DV5Dy5Dw.mjs";import{t as Ln}from"./removeUnverifiedRequestIdentifiers-B-Az2HJ8.mjs";import{t as Rn}from"./updateConsentManagerVersionToLatest-cryDInky.mjs";import{n as K,t as q}from"./mapConsentCsvRowsToInputs-CkLfxaIf.mjs";import{t as zn}from"./pullConsentManagerMetrics-DKCkfbCB.mjs";import{n as Bn,r as Vn,t as Hn}from"./buildXdiSyncEndpoint-CwABTp_y.mjs";import{t as Un}from"./consentManagersToBusinessEntities-DG0_eby5.mjs";import{i as Wn,n as Gn,r as Kn,t as qn}from"./uploadConsents-Din2Qdws.mjs";import{n as Jn,r as Yn,t as Xn}from"./pushCronIdentifiersFromCsv-D-l3erLO.mjs";import{t as Zn}from"./markRequestDataSiloIdsCompleted-CU0ba_mE.mjs";import{t as Qn}from"./generateCrossAccountApiKeys-B_p8gQ2C.mjs";import{t as $n}from"./validateTranscendAuth-D-dDINof.mjs";import{n as er,t as tr}from"./listFiles-DDGCBdtx.mjs";import{t as nr}from"./pullAllDatapoints-CNqQSSmF.mjs";import{t as rr}from"./pullManualEnrichmentIdentifiersToCsv-BSUXUp8v.mjs";import{n as ir,r as ar,t as or}from"./pushManualEnrichmentIdentifiersFromCsv-CqLIw5MV.mjs";import{chunk as sr,difference as cr}from"lodash-es";import{join as lr}from"node:path";import J from"colors";import{readFileSync as ur,readdirSync as dr,statSync as Y}from"node:fs";import{map as X,mapSeries as fr}from"@transcend-io/utils";import pr from"fast-glob";import{UPDATE_OR_CREATE_COOKIES as mr,buildTranscendGraphQLClient as Z,createSombraGotInstance as hr,fetchAllRequestIdentifiers as gr,fetchConsentManagerId as _r,makeGraphQLRequest as vr,syncCookies as yr,syncDataFlows as br,validateSombraVersion as xr}from"@transcend-io/sdk";import{execSync as Q}from"child_process";async function $(e,t){let n=await _r(e,{logger:b});await fr(sr(t,100),async t=>{await vr(e,mr,{variables:{airgapBundleId:n,cookies:t.map(e=>({name:e.name,trackingPurposes:e.trackingPurposes&&e.trackingPurposes.length>0?e.trackingPurposes:void 0,description:e.description,service:e.service,status:e.status,attributes:e.attributes,isRegex:e.isRegex}))},logger:b})})}async function Sr(e,t){let n=!1;b.info(J.magenta(`Syncing "${t.length}" cookies...`));let r=t.filter(e=>t.filter(t=>e.name===t.name&&e.isRegex===t.isRegex).length>1);if(r.length>0)throw Error(`Failed to upload cookies as there were non-unique entries found: ${r.map(({name:e})=>e).join(`,`)}`);try{b.info(J.magenta(`Upserting "${t.length}" new cookies...`)),await $(e,t),b.info(J.green(`Successfully synced ${t.length} cookies!`))}catch(e){n=!0,b.error(J.red(`Failed to create cookies! - ${e.message}`))}return!n}async function Cr({auth:e,sombraAuth:t,actions:n=[],statuses:r=[],identifierSearch:a,pageLimit:o=100,concurrency:s=1,transcendUrl:c=i,createdAtBefore:l,skipRequestIdentifiers:u=!1,createdAtAfter:d,updatedAtBefore:f,updatedAtAfter:p,isTest:m}){let h=Z(c,e),g=await hr(c,e,{logger:b,sombraApiKey:t,sombraUrl:process.env.SOMBRA_URL}),_=``;l&&(_+=` before ${l.toISOString()}`),d&&(_+=`${_?`, and`:``} after ${d.toISOString()}`),b.info(J.magenta(`${n.length>0?`Pulling requests of type "${n.join(`" , "`)}"`:`Pulling all requests`}${_}`));let v=s>1&&d&&l,y=v?W(d,l,s):[{createdAtAfter:d,createdAtBefore:l}];v&&b.info(J.magenta(`Splitting date range into ${s} parallel chunks`));let x=(await X(y,e=>U(h,{actions:n,text:a,statuses:r,createdAtBefore:e.createdAtBefore,createdAtAfter:e.createdAtAfter,updatedAtBefore:f,updatedAtAfter:p,isTest:m}),{concurrency:v?s:1})).flat();u||await xr(h,{logger:b});let S=u?x.map(e=>({...e,requestIdentifiers:[]})):await X(x,async e=>{let t=await gr(h,g,{filterBy:{requestId:e.id},skipSombraCheck:!0,logger:b});return{...e,requestIdentifiers:t}},{concurrency:o});return b.info(J.magenta(`Pulled ${S.length} requests`)),{requestsWithRequestIdentifiers:S,requestsFormattedForCsv:S.map(G)}}function wr(e,t=ur){try{return e?t(e,`utf8`):``}catch{return``}}async function Tr({auth:e,trackerStatus:t,file:n,classifyService:r=!1,transcendUrl:a=i}){let o=Z(a,e);b.info(J.magenta(`Reading "${n}" from disk`)),await br(o,K(H(n,w),t),{classifyService:r,logger:b})||(b.error(J.red(`Encountered error(s) syncing data flows from CSV, see logs above for more info. `)),process.exit(1))}async function Er({auth:e,trackerStatus:t,file:n,transcendUrl:r=i}){let a=Z(r,e);b.info(J.magenta(`Reading "${n}" from disk`)),await yr(a,q(H(n,C),t),{logger:b})||(b.error(J.red(`Encountered error(s) syncing cookies from CSV, see logs above for more info. `)),process.exit(1))}function Dr(e){return dr(e).filter(t=>Y(lr(e,t)).isDirectory())}function Or(e){return e.replace(/(https?:\/\/[^\s]+)/g,`<link-omitted>`)}function kr(e){return Object.entries(e).reduce((e,[t,n])=>n!=null&&n!==``&&!(Array.isArray(n)&&n.length===0)&&!(typeof n==`object`&&Object.keys(n).length===0)?Object.assign(e,{[t]:n}):e,{})}function Ar({baseBranch:e,rootDirectory:t,githubRepo:n,excludedGlob:r=[],fileBlockList:i=[]}){Q(`git fetch origin ${e}`);let a=Q(`git ls-remote ${n} "refs/heads/${e}" | cut -f 1`,{encoding:`utf-8`}).split(`
|
|
2
2
|
`)[0],o=Q(`git rev-parse HEAD`,{encoding:`utf-8`}).split(`
|
|
3
3
|
`)[0];if(!a||!o)throw Error(`FAILED TO FIND COMMIT RANGE`);let s=cr(Q(`git fetch && git diff --name-only "${e||a}...${o}" -- ${t}`,{encoding:`utf-8`}).split(`
|
|
4
4
|
`).filter(e=>e),i),c=r.length>0?pr.sync(s,{ignore:r}):s,l={};return c.forEach(e=>{l[e]=Q(`git show ${o}:${e}`,{encoding:`utf-8`})}),{changedFiles:s,fileDiffs:l,repoName:n.split(`/`).pop().split(`.`)[0],commit:o}}export{v as ADMIN_DASH,p as ADMIN_DASH_DATAPOINTS,g as ADMIN_DASH_INTEGRATIONS,Bt as APPROVE_PRIVACY_REQUEST,Vt as ASSESSMENT_TEMPLATES,Be as ActionInput,ke as ActionItemCollectionInput,Ie as ActionItemInput,xe as ApiKeyInput,I as AssessmentAnswerOptionInput,je as AssessmentDisplayLogicInput,Re as AssessmentInput,z as AssessmentNestedRuleInput,Te as AssessmentResourceInput,He as AssessmentRetentionScheduleInput,V as AssessmentRuleInput,he as AssessmentSectionInput,Ne as AssessmentSectionQuestionInput,De as AssessmentTemplateInput,St as AttestedExtraIdentifiers,ye as AttributeInput,_e as AttributePreview,F as AttributeValueInput,mt as BLANK,ut as BULK_APPLY,We as BusinessEntityInput,Lt as CANCEL_PRIVACY_REQUEST,ft as CAN_APPLY_IN_BULK,ct as CachedFileState,ot as CachedRequestState,qe as CodePackageInput,R as CodePackageSdk,dt as ColumnName,Ke as ConsentManageExperienceInput,D as ConsentManagerInput,te as ConsentManagerServiceMetadata,Ge as ConsentPreferenceTopic,O as ConsentPreferenceTopicOptionValue,ne as ConsentPurpose,C as CookieCsvInput,de as CookieInput,Xt as CronIdentifier,Jn as CronIdentifierPush,Zt as CronPartition,u as DEBUG,i as DEFAULT_TRANSCEND_API,m as DEFAULT_TRANSCEND_API_KEY,_ as DEFAULT_TRANSCEND_CONSENT_API,Wt as DEFAULT_TRANSCEND_PULL_RESOURCES,we as DataCategoryGuessInput,S as DataCategoryInput,Ce as DataCategoryPreviewInput,ue as DataCategoryRecommendationInput,w as DataFlowCsvInput,fe as DataFlowInput,re as DataSiloInput,oe as DataSubjectInput,le as DatapointInput,E as DeletionDependencies,Je as DeletionDependency,ee as DeletionDependencyInput,A as DeletionDependencyReset,Nt as ENTRY_COUNT,ir as EnrichPrivacyRequest,ae as EnricherInput,k as FieldInput,pt as IDENTIFIER_BLOCK_LIST,Hn as IP_ADDRESS_REGEX,st as IS_REQUIRED,j as IdentifierInput,ie as ImportOnetrustAssessmentsInput,cn as IntlMessage,ce as IntlMessageInput,lt as NONE,zt as NOTIFY_ADDITIONAL_TIME,r as OneTrustFileFormat,t as OneTrustPullResource,n as OneTrustPullSource,jt as ParsedAttributeInput,Fe as PartitionInput,se as PolicyInput,N as PreferenceWorkflowConfigInput,M as PreferenceWorkflowConfigPurposeInput,P as PreflightInput,T as PrivacyCenterInput,qt as PrivacyRequest,vt as PrivacyRequestInput,un as PrivacyRequestResponse,x as ProcessingActivityInput,me as ProcessingPurposeInput,Ve as ProcessingPurposePreviewInput,Ae as PromptAVendorEmailSettings,qn as PurposeMap,It as REQUESTS,Ft as REQUESTS_COUNT,a as RETRYABLE_BATCH_STATUSES,Le as RegionInput,Se as RepositoryInput,an as RequestFileMetadata,rn as RequestFileMetadataResponse,Kt as RequestPurposeTrigger,L as RiskAssignmentInput,Me as RiskLogicInput,d as SCOPES_BY_TITLE,y as SCOPE_TITLES,ze as SiloDiscoveryResultInput,B as SoftwareDevelopmentKitInput,Ee as StoredApiKey,ht as SuccessfulRequest,o as TR_PULL_RESOURCE_SCOPE_MAP,s as TR_PUSH_RESOURCE_SCOPE_MAP,h as TR_YML_RESOURCE_TO_FIELD_NAME,Ue as TeamInput,pe as TemplateInput,ge as TranscendInput,e as TranscendPullResource,Rt as UPDATE_PRIVACY_REQUEST,Gn as USP_STRING_REGEX,tt as VARIABLE_PARAMETERS_NAME,Ze as VARIABLE_PARAMETERS_REGEXP,Pe as VendorInput,Oe as WebhookHeader,be as WorkflowConfigInput,Fn as appendCsvRowsOrdered,jn as appendCsvSync,pn as approvePrivacyRequests,Bn as buildXdiSyncEndpoint,_n as bulkRestartRequests,yn as bulkRetryEnrichers,hn as cancelPrivacyRequests,Dn as collectCsvFilesOrExit,En as collectParquetFilesOrExit,Un as consentManagersToBusinessEntities,tn as createCodePackage,Wn as createConsentToken,l as description,Vn as domainToHost,ln as downloadPrivacyRequestFiles,ar as enrichPrivacyRequest,nt as ensureAllDataSubjectsExist,fn as extractClientError,Ut as fetchAllAssessmentTemplates,U as fetchAllRequests,Jt as fetchRequestDataSiloActiveCount,Gt as fetchRequestsTotalCount,tr as filterFileNames,kr as filterNullishValuesFromObject,kt as filterRows,G as formatRequestForCsv,_t as fuzzyMatchColumns,gt as fuzzySearch,Qn as generateCrossAccountApiKeys,sn as getFileMetadataForPrivacyRequests,Ar as getGitFilesThatChanged,bt as getUniqueValuesForColumn,Nn as initCsvFile,wn as inquirerAutoComplete,Sn as inquirerConfirmBoolean,Cn as inquirerConfirmText,Dr as listDirectories,er as listFiles,wt as mapColumnsToAttributes,Et as mapColumnsToIdentifiers,q as mapCookieCsvRowsToInputs,Ct as mapCsvColumnsToApi,Tt as mapCsvRowsToRequestInputs,K as mapDataFlowCsvRowsToInputs,xt as mapEnumValues,yt as mapRequestEnumValues,Yn as markCronIdentifierCompleted,Zn as markRequestDataSiloIdsCompleted,nn as markSilentPrivacyRequests,at as mergeTranscendInputs,f as name,Dt as normalizeIdentifierValue,ve as normalizeTranscendInput,mn as notifyPrivacyRequestsAdditionalTime,On as parquetToCsvOneFile,At as parseAttributesFromString,Mt as parseCsv,An as parseFilePath,$e as parseTranscendYaml,Tn as parseVariablesFromString,nr as pullAllDatapoints,Qt as pullChunkedCustomSiloOutstandingIdentifiers,zn as pullConsentManagerMetrics,Yt as pullCronPageOfIdentifiers,rr as pullManualEnrichmentIdentifiersToCsv,Cr as pullPrivacyRequests,Ht as pullTranscendConfiguration,Pt as pullUnstructuredSubDataPointRecommendations,Xn as pushCronIdentifiersFromCsv,or as pushManualEnrichmentIdentifiersFromCsv,H as readCsv,wr as readSafe,Xe as readTranscendYaml,Or as removeLinks,Ln as removeUnverifiedRequestIdentifiers,Ye as replaceVariablesInYaml,gn as restartPrivacyRequest,bn as retryRequestDataSilos,Qe as serializeTranscendYaml,vn as skipPreflightJobs,In as skipRequestDataSilos,W as splitDateRange,on as streamPrivacyRequestFiles,xn as streamPrivacyRequestsToCsv,dn as submitPrivacyRequest,$t as syncCodePackages,rt as syncConfigurationToTranscend,Sr as syncCookies,it as syncDataSilos,en as updateCodePackages,Rn as updateConsentManagerVersionToLatest,$ as updateOrCreateCookies,Kn as uploadConsents,Er as uploadCookiesFromCsv,Tr as uploadDataFlowsFromCsv,Ot as uploadPrivacyRequestsFromCsv,$n as validateTranscendAuth,c as version,kn as writeCsv,Mn as writeCsvSync,Pn as writeLargeCsv,et as writeTranscendYaml};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e}from"./constants-
|
|
2
|
-
//# sourceMappingURL=markRequestDataSiloIdsCompleted-
|
|
1
|
+
import{a as e}from"./constants-boymUaFC.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{RequestDataSiloStatus as n}from"@transcend-io/privacy-types";import r from"colors";import{map as i}from"@transcend-io/utils";import{CHANGE_REQUEST_DATA_SILO_STATUS as a,buildTranscendGraphQLClient as o,fetchRequestDataSilo as s,makeGraphQLRequest as c}from"@transcend-io/sdk";import l from"cli-progress";async function u({requestIds:u,dataSiloId:d,auth:f,concurrency:p=100,status:m=n.Resolved,transcendUrl:h=e}){let g=o(h,f),_=new Date().getTime(),v=new l.SingleBar({},l.Presets.shades_classic);t.info(r.magenta(`Notifying Transcend for data silo "${d}" marking "${u.length}" requests as completed.`));let y=0;v.start(u.length,0),await i(u,async e=>{let n=await s(g,{logger:t,filterBy:{requestId:e,dataSiloId:d}});try{await c(g,a,{variables:{requestDataSiloId:n.id,status:m},logger:t})}catch(e){if(!e.message.includes(`Client error: Request must be active:`)&&!e.message.includes(`Failed to find RequestDataSilo`))throw e}y+=1,v.update(y)},{concurrency:p}),v.stop();let b=new Date().getTime()-_;return t.info(r.green(`Successfully notified Transcend in "${b/1e3}" seconds!`)),u.length}export{u as t};
|
|
2
|
+
//# sourceMappingURL=markRequestDataSiloIdsCompleted-CU0ba_mE.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markRequestDataSiloIdsCompleted-
|
|
1
|
+
{"version":3,"file":"markRequestDataSiloIdsCompleted-CU0ba_mE.mjs","names":[],"sources":["../src/lib/cron/markRequestDataSiloIdsCompleted.ts"],"sourcesContent":["import { RequestDataSiloStatus } from '@transcend-io/privacy-types';\nimport {\n buildTranscendGraphQLClient,\n makeGraphQLRequest,\n CHANGE_REQUEST_DATA_SILO_STATUS,\n fetchRequestDataSilo,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\n\n/**\n * Given a CSV of Request IDs, mark associated RequestDataSilos as completed\n *\n * @param options - Options\n * @returns Number of items marked as completed\n */\nexport async function markRequestDataSiloIdsCompleted({\n requestIds,\n dataSiloId,\n auth,\n concurrency = 100,\n status = RequestDataSiloStatus.Resolved,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** The list of request ids to mark as completed */\n requestIds: string[];\n /** Transcend API key authentication */\n auth: string;\n /** Data Silo ID to pull down jobs for */\n dataSiloId: string;\n /** Status to update requests to */\n status?: RequestDataSiloStatus;\n /** Upload concurrency */\n concurrency?: number;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n}): Promise<number> {\n // Find all requests made before createdAt that are in a removing data state\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n // Time duration\n const t0 = new Date().getTime();\n // create a new progress bar instance and use shades_classic theme\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n\n // Notify Transcend\n logger.info(\n colors.magenta(\n `Notifying Transcend for data silo \"${dataSiloId}\" marking \"${requestIds.length}\" requests as completed.`,\n ),\n );\n\n let total = 0;\n progressBar.start(requestIds.length, 0);\n await map(\n requestIds,\n async (requestId) => {\n const requestDataSilo = await fetchRequestDataSilo(client, {\n logger,\n filterBy: { requestId, dataSiloId },\n });\n\n try {\n await makeGraphQLRequest<{\n /** Whether we successfully uploaded the results */\n success: boolean;\n }>(client, CHANGE_REQUEST_DATA_SILO_STATUS, {\n variables: {\n requestDataSiloId: requestDataSilo.id,\n status,\n },\n logger,\n });\n } catch (err) {\n if (\n !err.message.includes('Client error: Request must be active:') &&\n !err.message.includes('Failed to find RequestDataSilo')\n ) {\n throw err;\n }\n }\n\n total += 1;\n progressBar.update(total);\n },\n { concurrency },\n );\n\n progressBar.stop();\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(colors.green(`Successfully notified Transcend in \"${totalTime / 1000}\" seconds!`));\n return requestIds.length;\n}\n"],"mappings":"yYAoBA,eAAsB,EAAgC,CACpD,aACA,aACA,OACA,cAAc,IACd,SAAS,EAAsB,SAC/B,eAAe,GAcG,CAElB,IAAM,EAAS,EAA4B,EAAc,EAAK,CAGxD,EAAK,IAAI,MAAM,CAAC,SAAS,CAEzB,EAAc,IAAI,EAAY,UAAU,EAAE,CAAE,EAAY,QAAQ,eAAe,CAGrF,EAAO,KACL,EAAO,QACL,sCAAsC,EAAW,aAAa,EAAW,OAAO,0BACjF,CACF,CAED,IAAI,EAAQ,EACZ,EAAY,MAAM,EAAW,OAAQ,EAAE,CACvC,MAAM,EACJ,EACA,KAAO,IAAc,CACnB,IAAM,EAAkB,MAAM,EAAqB,EAAQ,CACzD,SACA,SAAU,CAAE,YAAW,aAAY,CACpC,CAAC,CAEF,GAAI,CACF,MAAM,EAGH,EAAQ,EAAiC,CAC1C,UAAW,CACT,kBAAmB,EAAgB,GACnC,SACD,CACD,SACD,CAAC,OACK,EAAK,CACZ,GACE,CAAC,EAAI,QAAQ,SAAS,wCAAwC,EAC9D,CAAC,EAAI,QAAQ,SAAS,iCAAiC,CAEvD,MAAM,EAIV,GAAS,EACT,EAAY,OAAO,EAAM,EAE3B,CAAE,cAAa,CAChB,CAED,EAAY,MAAM,CAElB,IAAM,EADK,IAAI,MAAM,CAAC,SACF,CAAG,EAGvB,OADA,EAAO,KAAK,EAAO,MAAM,uCAAuC,EAAY,IAAK,YAAY,CAAC,CACvF,EAAW"}
|
package/dist/{markSilentPrivacyRequests-CPU4Q2lF.mjs → markSilentPrivacyRequests-Bf_4pCaq.mjs}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e}from"./constants-
|
|
2
|
-
//# sourceMappingURL=markSilentPrivacyRequests-
|
|
1
|
+
import{a as e}from"./constants-boymUaFC.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{o as n}from"./request-BpKyN9Xm.mjs";import{r}from"./fetchAllRequests-BI0tFl_a.mjs";import{RequestStatus as i}from"@transcend-io/privacy-types";import a from"colors";import{map as o}from"@transcend-io/utils";import{buildTranscendGraphQLClient as s,makeGraphQLRequest as c}from"@transcend-io/sdk";import l from"cli-progress";async function u({requestActions:u,auth:d,requestIds:f,statuses:p=[i.Compiling,i.RequestMade,i.Delayed,i.Approving,i.Secondary,i.Enriching,i.Waiting,i.SecondaryApproving],createdAtAfter:m,createdAtBefore:h,updatedAtBefore:g,updatedAtAfter:_,concurrency:v=100,transcendUrl:y=e}){let b=s(y,d),x=new Date().getTime(),S=new l.SingleBar({},l.Presets.shades_classic),C=await r(b,{actions:u,statuses:p,createdAtBefore:h,createdAtAfter:m,updatedAtBefore:g,updatedAtAfter:_,isSilent:!1,requestIds:f});t.info(a.magenta(`Marking "${C.length}" as silent mode.`));let w=0;S.start(C.length,0),await o(C,async e=>{await c(b,n,{variables:{input:{id:e.id,isSilent:!0}},logger:t}),w+=1,S.update(w)},{concurrency:v}),S.stop();let T=new Date().getTime()-x;return t.info(a.green(`Successfully marked ${w} requests as silent mode in "${T/1e3}" seconds!`)),C.length}export{u as t};
|
|
2
|
+
//# sourceMappingURL=markSilentPrivacyRequests-Bf_4pCaq.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markSilentPrivacyRequests-
|
|
1
|
+
{"version":3,"file":"markSilentPrivacyRequests-Bf_4pCaq.mjs","names":[],"sources":["../src/lib/requests/markSilentPrivacyRequests.ts"],"sourcesContent":["import { RequestAction, RequestStatus } from '@transcend-io/privacy-types';\nimport { buildTranscendGraphQLClient, makeGraphQLRequest } from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { UPDATE_PRIVACY_REQUEST, fetchAllRequests } from '../graphql/index.js';\n\n/**\n * Mark a set of privacy requests to be in silent mode\n *\n * @param options - Options\n * @returns The number of requests marked silent\n */\nexport async function markSilentPrivacyRequests({\n requestActions,\n auth,\n requestIds,\n statuses = [\n RequestStatus.Compiling,\n RequestStatus.RequestMade,\n RequestStatus.Delayed,\n RequestStatus.Approving,\n RequestStatus.Secondary,\n RequestStatus.Enriching,\n RequestStatus.Waiting,\n RequestStatus.SecondaryApproving,\n ],\n createdAtAfter,\n createdAtBefore,\n updatedAtBefore,\n updatedAtAfter,\n concurrency = 100,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** The request actions that should be restarted */\n requestActions: RequestAction[];\n /** Transcend API key authentication */\n auth: string;\n /** Concurrency limit for approving */\n concurrency?: number;\n /** The request statuses to mark silent */\n statuses?: RequestStatus[];\n /** The set of privacy requests to mark silent */\n requestIds?: string[];\n /** Filter for requests created before this date */\n createdAtBefore?: Date;\n /** Filter for requests created after this date */\n createdAtAfter?: Date;\n /** Filter for requests updated before this date */\n updatedAtBefore?: Date;\n /** Filter for requests updated after this date */\n updatedAtAfter?: Date;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n}): Promise<number> {\n // Find all requests made before createdAt that are in a removing data state\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n // Time duration\n const t0 = new Date().getTime();\n // create a new progress bar instance and use shades_classic theme\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n\n // Pull in the requests\n const allRequests = await fetchAllRequests(client, {\n actions: requestActions,\n statuses,\n createdAtBefore,\n createdAtAfter,\n updatedAtBefore,\n updatedAtAfter,\n isSilent: false,\n requestIds,\n });\n\n // Notify Transcend\n logger.info(colors.magenta(`Marking \"${allRequests.length}\" as silent mode.`));\n\n let total = 0;\n progressBar.start(allRequests.length, 0);\n await map(\n allRequests,\n async (requestToMarkSilent) => {\n await makeGraphQLRequest(client, UPDATE_PRIVACY_REQUEST, {\n variables: {\n input: {\n id: requestToMarkSilent.id,\n isSilent: true,\n },\n },\n logger,\n });\n\n total += 1;\n progressBar.update(total);\n },\n { concurrency },\n );\n\n progressBar.stop();\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(\n colors.green(\n `Successfully marked ${total} requests as silent mode in \"${totalTime / 1000}\" seconds!`,\n ),\n );\n return allRequests.length;\n}\n"],"mappings":"4ZAgBA,eAAsB,EAA0B,CAC9C,iBACA,OACA,aACA,WAAW,CACT,EAAc,UACd,EAAc,YACd,EAAc,QACd,EAAc,UACd,EAAc,UACd,EAAc,UACd,EAAc,QACd,EAAc,mBACf,CACD,iBACA,kBACA,kBACA,iBACA,cAAc,IACd,eAAe,GAsBG,CAElB,IAAM,EAAS,EAA4B,EAAc,EAAK,CAGxD,EAAK,IAAI,MAAM,CAAC,SAAS,CAEzB,EAAc,IAAI,EAAY,UAAU,EAAE,CAAE,EAAY,QAAQ,eAAe,CAG/E,EAAc,MAAM,EAAiB,EAAQ,CACjD,QAAS,EACT,WACA,kBACA,iBACA,kBACA,iBACA,SAAU,GACV,aACD,CAAC,CAGF,EAAO,KAAK,EAAO,QAAQ,YAAY,EAAY,OAAO,mBAAmB,CAAC,CAE9E,IAAI,EAAQ,EACZ,EAAY,MAAM,EAAY,OAAQ,EAAE,CACxC,MAAM,EACJ,EACA,KAAO,IAAwB,CAC7B,MAAM,EAAmB,EAAQ,EAAwB,CACvD,UAAW,CACT,MAAO,CACL,GAAI,EAAoB,GACxB,SAAU,GACX,CACF,CACD,SACD,CAAC,CAEF,GAAS,EACT,EAAY,OAAO,EAAM,EAE3B,CAAE,cAAa,CAChB,CAED,EAAY,MAAM,CAElB,IAAM,EADK,IAAI,MAAM,CAAC,SACF,CAAG,EAOvB,OALA,EAAO,KACL,EAAO,MACL,uBAAuB,EAAM,+BAA+B,EAAY,IAAK,YAC9E,CACF,CACM,EAAY"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e}from"./constants-
|
|
2
|
-
//# sourceMappingURL=notifyPrivacyRequestsAdditionalTime-
|
|
1
|
+
import{a as e}from"./constants-boymUaFC.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{r as n}from"./request-BpKyN9Xm.mjs";import{r}from"./fetchAllRequests-BI0tFl_a.mjs";import{RequestAction as i}from"@transcend-io/privacy-types";import a from"colors";import{map as o}from"@transcend-io/utils";import{buildTranscendGraphQLClient as s,fetchAllTemplates as c,makeGraphQLRequest as l}from"@transcend-io/sdk";import u from"cli-progress";async function d({requestActions:d=Object.values(i),auth:f,requestIds:p,createdAtBefore:m,days:h=45,daysLeft:g=10,createdAtAfter:_,updatedAtBefore:v,updatedAtAfter:y,emailTemplate:b=`Additional Time Needed`,concurrency:x=100,transcendUrl:S=e}){let C=s(S,f),w=new Date().getTime(),T=new u.SingleBar({},u.Presets.shades_classic),E=(await c(C,{logger:t,filterBy:{title:b}})).find(e=>e.title===b);if(!E)throw Error(`Failed to find a template with title: "${b}"`);let D=await r(C,{actions:d,createdAtBefore:m,createdAtAfter:_,updatedAtBefore:v,updatedAtAfter:y,isSilent:!1,isClosed:!1,requestIds:p});D=D.filter(e=>typeof e.daysRemaining==`number`&&e.daysRemaining<g),t.info(a.magenta(`Notifying "${D.length}" that more time is needed.`));let O=0;T.start(D.length,0),await o(D,async e=>{await l(C,n,{variables:{input:{requestId:e.id,template:E.template.defaultMessage,subject:E.subject.defaultMessage,additionalTime:h}},logger:t}),O+=1,T.update(O)},{concurrency:x}),T.stop();let k=new Date().getTime()-w;return t.info(a.green(`Successfully marked ${O} requests as silent mode in "${k/1e3}" seconds!`)),D.length}export{d as t};
|
|
2
|
+
//# sourceMappingURL=notifyPrivacyRequestsAdditionalTime-rnUXdvBR.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifyPrivacyRequestsAdditionalTime-
|
|
1
|
+
{"version":3,"file":"notifyPrivacyRequestsAdditionalTime-rnUXdvBR.mjs","names":[],"sources":["../src/lib/requests/notifyPrivacyRequestsAdditionalTime.ts"],"sourcesContent":["import { RequestAction } from '@transcend-io/privacy-types';\nimport {\n buildTranscendGraphQLClient,\n fetchAllTemplates,\n makeGraphQLRequest,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { NOTIFY_ADDITIONAL_TIME, fetchAllRequests } from '../graphql/index.js';\n\n/**\n * Mark a set of privacy requests to be in silent mode.\n * Note requests in silent mode are ignored\n *\n * @param options - Options\n * @returns The number of requests marked silent\n */\nexport async function notifyPrivacyRequestsAdditionalTime({\n requestActions = Object.values(RequestAction),\n auth,\n requestIds,\n createdAtBefore,\n days = 45,\n daysLeft = 10,\n createdAtAfter,\n updatedAtBefore,\n updatedAtAfter,\n emailTemplate = 'Additional Time Needed',\n concurrency = 100,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** The request actions that should be restarted */\n requestActions?: RequestAction[];\n /** Filter for requests created before this date */\n createdAtBefore: Date;\n /** Filter for requests created after this date */\n createdAtAfter?: Date;\n /** Filter for requests updated before this date */\n updatedAtBefore?: Date;\n /** Filter for requests updated after this date */\n updatedAtAfter?: Date;\n /** Email template */\n emailTemplate?: string;\n /** Transcend API key authentication */\n auth: string;\n /** Number of days to extend request by */\n days?: number;\n /**\n * Only notify requests that have less than this number of days until they are considered expired.\n * This allows for re-running the command without notifying the same users multiple times\n */\n daysLeft?: number;\n /** Concurrency limit for approving */\n concurrency?: number;\n /** The set of privacy requests to notify */\n requestIds?: string[];\n /** API URL for Transcend backend */\n transcendUrl?: string;\n}): Promise<number> {\n // Find all requests made before createdAt that are in a removing data state\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n // Time duration\n const t0 = new Date().getTime();\n // create a new progress bar instance and use shades_classic theme\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n\n // Grab the template with that title\n const matchingTemplates = await fetchAllTemplates(client, {\n logger,\n filterBy: { title: emailTemplate },\n });\n const exactTemplateMatch = matchingTemplates.find((template) => template.title === emailTemplate);\n if (!exactTemplateMatch) {\n throw new Error(`Failed to find a template with title: \"${emailTemplate}\"`);\n }\n\n // Pull in the requests\n let allRequests = await fetchAllRequests(client, {\n actions: requestActions,\n createdAtBefore,\n createdAtAfter,\n updatedAtBefore,\n updatedAtAfter,\n isSilent: false,\n isClosed: false,\n requestIds,\n });\n\n // Filter requests by daysLeft\n allRequests = allRequests.filter(\n (request) => typeof request.daysRemaining === 'number' && request.daysRemaining < daysLeft,\n );\n\n // Notify Transcend\n logger.info(colors.magenta(`Notifying \"${allRequests.length}\" that more time is needed.`));\n\n let total = 0;\n progressBar.start(allRequests.length, 0);\n await map(\n allRequests,\n async (requestToNotify) => {\n await makeGraphQLRequest(client, NOTIFY_ADDITIONAL_TIME, {\n variables: {\n input: {\n requestId: requestToNotify.id,\n template: exactTemplateMatch.template.defaultMessage,\n subject: exactTemplateMatch.subject.defaultMessage,\n additionalTime: days,\n },\n },\n logger,\n });\n\n total += 1;\n progressBar.update(total);\n },\n { concurrency },\n );\n\n progressBar.stop();\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(\n colors.green(\n `Successfully marked ${total} requests as silent mode in \"${totalTime / 1000}\" seconds!`,\n ),\n );\n return allRequests.length;\n}\n"],"mappings":"mbAqBA,eAAsB,EAAoC,CACxD,iBAAiB,OAAO,OAAO,EAAc,CAC7C,OACA,aACA,kBACA,OAAO,GACP,WAAW,GACX,iBACA,kBACA,iBACA,gBAAgB,yBAChB,cAAc,IACd,eAAe,GA6BG,CAElB,IAAM,EAAS,EAA4B,EAAc,EAAK,CAGxD,EAAK,IAAI,MAAM,CAAC,SAAS,CAEzB,EAAc,IAAI,EAAY,UAAU,EAAE,CAAE,EAAY,QAAQ,eAAe,CAO/E,GAAqB,MAJK,EAAkB,EAAQ,CACxD,SACA,SAAU,CAAE,MAAO,EAAe,CACnC,CAAC,EAC2C,KAAM,GAAa,EAAS,QAAU,EAAc,CACjG,GAAI,CAAC,EACH,MAAU,MAAM,0CAA0C,EAAc,GAAG,CAI7E,IAAI,EAAc,MAAM,EAAiB,EAAQ,CAC/C,QAAS,EACT,kBACA,iBACA,kBACA,iBACA,SAAU,GACV,SAAU,GACV,aACD,CAAC,CAGF,EAAc,EAAY,OACvB,GAAY,OAAO,EAAQ,eAAkB,UAAY,EAAQ,cAAgB,EACnF,CAGD,EAAO,KAAK,EAAO,QAAQ,cAAc,EAAY,OAAO,6BAA6B,CAAC,CAE1F,IAAI,EAAQ,EACZ,EAAY,MAAM,EAAY,OAAQ,EAAE,CACxC,MAAM,EACJ,EACA,KAAO,IAAoB,CACzB,MAAM,EAAmB,EAAQ,EAAwB,CACvD,UAAW,CACT,MAAO,CACL,UAAW,EAAgB,GAC3B,SAAU,EAAmB,SAAS,eACtC,QAAS,EAAmB,QAAQ,eACpC,eAAgB,EACjB,CACF,CACD,SACD,CAAC,CAEF,GAAS,EACT,EAAY,OAAO,EAAM,EAE3B,CAAE,cAAa,CAChB,CAED,EAAY,MAAM,CAElB,IAAM,EADK,IAAI,MAAM,CAAC,SACF,CAAG,EAOvB,OALA,EAAO,KACL,EAAO,MACL,uBAAuB,EAAM,+BAA+B,EAAY,IAAK,YAC9E,CACF,CACM,EAAY"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{c as e,l as t,m as n}from"./policy-project-discovery-
|
|
1
|
+
import{c as e,l as t,m as n}from"./policy-project-discovery-oc49b2gr.mjs";const r=/^\s*package\s+/mu;function i(e,t){let n=/^[A-Za-z_][A-Za-z0-9_]*/u.exec(e.slice(t));return n?{identifier:n[0],end:t+n[0].length}:void 0}function a(e,t){let n=t;for(;n<e.length&&/\s/u.test(e[n]);)n+=1;return n}function o(e,t){if(e[t]==="`"){let n=e.indexOf("`",t+1);return n===-1?void 0:{value:e.slice(t+1,n),end:n+1}}if(e[t]!==`"`)return;let n=t+1;for(;n<e.length;){if(e[n]===`\\`){n+=2;continue}if(e[n]===`"`){let r=e.slice(t,n+1);try{return{value:JSON.parse(r),end:n+1}}catch{return}}n+=1}}function s(e,t){let n=i(e,t);if(!n)return;let r=[n.identifier],s=n.end;for(;s<e.length;){let t=a(e,s);if(e[t]===`.`){let n=i(e,a(e,t+1));if(!n)break;r.push(n.identifier),s=n.end;continue}if(e[t]===`[`){let n=o(e,a(e,t+1));if(!n)break;let i=a(e,n.end);if(e[i]!==`]`)break;r.push(n.value),s=i+1;continue}break}return{source:e.slice(t,s),segments:r,end:s}}function c(e){let t=r.exec(e);if(!t)return;let n=s(e,t.index+t[0].length);return n?{source:n.source,segments:n.segments}:void 0}function l(e,t){let n=e[t],r=t+1;for(;r<e.length;){if(n===`"`&&e[r]===`\\`){r+=2;continue}if(e[r]===n)return r+1;r+=1}return r}function u(e){let t=[],n=0;for(;n<e.length;){if(e[n]===`#`){let t=e.indexOf(`
|
|
2
2
|
`,n);n=t===-1?e.length:t+1;continue}if(e[n]===`"`||e[n]==="`"){n=l(e,n);continue}let r=i(e,n);if(!r){n+=1;continue}if(r.identifier!==`data`){n=r.end;continue}let a=s(e,n);if(!a||a.segments.length===1){n=r.end;continue}let o=e.lastIndexOf(`
|
|
3
3
|
`,n-1)+1;/^\s*import\s+$/u.test(e.slice(o,n))||t.push({reference:a,line:e.slice(0,n).split(`
|
|
4
4
|
`).length}),n=a.end}return t}function d(e){return e.endsWith(`.rego`)&&!e.endsWith(`_test.rego`)}function f(t){if(t===void 0)throw Error(`Policy bundle directory must contain a ${e} file.`);let n;try{n=JSON.parse(t)}catch(t){throw Error(`${e} is not valid JSON: ${t instanceof Error?t.message:String(t)}`,{cause:t})}if(!n||typeof n!=`object`||Array.isArray(n))throw Error(`${e} must contain a JSON object.`);let r=n.roots;if(!Array.isArray(r)||r.length===0)throw Error(`${e} must declare "roots" as a non-empty array of strings (e.g. {"roots":["policy_engine"]}).`);if(!r.every(e=>typeof e==`string`&&e.length>0))throw Error(`${e} "roots" must be an array of non-empty strings.`);if(!r.every(e=>e.split(`/`).every(e=>/^[A-Za-z_][A-Za-z0-9_]*$/u.test(e))))throw Error(`${e} "roots" must contain slash-separated Rego identifiers so Policy Engine can re-namespace them safely.`);let i=p(n);return i===void 0?{roots:r}:{roots:r,template:i}}function p(e){let r=e.metadata;if(!r||typeof r!=`object`||Array.isArray(r))return;let i=r[t];if(!i||typeof i!=`object`||Array.isArray(i))return;let a=i.template;if(typeof a==`string`)return n.includes(a)?a:void 0}function m(e,t){return e.length>=t.length&&t.every((t,n)=>t===e[n])}function h(e,t){let n=t.join(`.`),r=e.source.slice(n.length);return e.source.startsWith(n)&&(r===``||r.startsWith(`.`)||/^\s*\[/u.test(r))}function g(e,t){return m(e.slice(1),t)}function _(t,n){let r=f(t),i=r.roots.join(`, `),a=n.filter(({path:e})=>d(e)).slice().sort((e,t)=>e.path.localeCompare(t.path));if(a.length===0)throw Error(`Policy bundle directory must contain at least one .rego policy file.`);let o=r.roots.map(e=>e.split(`/`)),s=a.flatMap(({path:e,contents:t})=>{let n=c(t);return!n||o.some(e=>h(n,e))?[]:[` - ${e} (package ${n.source}) is not covered by roots [${i}]`]});if(s.length>0)throw Error([`${e} "roots" do not cover all Rego packages in the bundle; uncovered packages will fail-closed at decide time. Either broaden "roots" or move the policy under a covered package:`,...s].join(`
|
|
5
5
|
`));let l=a.flatMap(({path:e,contents:t})=>u(t).filter(({reference:e})=>o.some(t=>g(e.segments,t))).map(({reference:t,line:n})=>` - ${e}:${n} references ${t.source}; import that package and use its local name`));if(l.length>0)throw Error([`Publishable Rego must not reference its declared bundle roots directly because Policy Engine re-namespaces package imports during activation:`,...l].join(`
|
|
6
6
|
`));return{manifest:r,publishableRegoPaths:a.map(({path:e})=>e)}}export{_ as n,f as t};
|
|
7
|
-
//# sourceMappingURL=policy-bundle-manifest-
|
|
7
|
+
//# sourceMappingURL=policy-bundle-manifest-B1Herd_n.mjs.map
|
package/dist/{policy-bundle-manifest-BDey6EBs.mjs.map → policy-bundle-manifest-B1Herd_n.mjs.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy-bundle-manifest-BDey6EBs.mjs","names":[],"sources":["../src/lib/policy/rego-reference.ts","../src/lib/policy/policy-bundle-manifest.ts"],"sourcesContent":["/** Parsed static Rego reference. */\ninterface StaticRegoReference {\n /** Source representation of the reference. */\n source: string;\n /** Logical document path segments. */\n segments: string[];\n /** Offset immediately after the reference. */\n end: number;\n}\n\n/** Matches the beginning of a Rego package declaration. */\nconst PACKAGE_DECLARATION_PATTERN = /^\\s*package\\s+/mu;\n\n/**\n * Read an identifier at a source offset.\n *\n * @param source - Rego source\n * @param start - Candidate identifier offset\n * @returns Identifier and following offset\n */\nfunction readIdentifier(\n source: string,\n start: number,\n): { identifier: string; end: number } | undefined {\n const match = /^[A-Za-z_][A-Za-z0-9_]*/u.exec(source.slice(start));\n return match ? { identifier: match[0], end: start + match[0].length } : undefined;\n}\n\n/**\n * Advance past Rego whitespace.\n *\n * @param source - Rego source\n * @param start - Initial source offset\n * @returns First non-whitespace offset\n */\nfunction skipWhitespace(source: string, start: number): number {\n let current = start;\n while (current < source.length && /\\s/u.test(source[current]!)) {\n current += 1;\n }\n return current;\n}\n\n/**\n * Read a quoted or raw Rego string.\n *\n * @param source - Rego source\n * @param start - Opening quote offset\n * @returns Decoded string and following offset\n */\nfunction readQuotedString(\n source: string,\n start: number,\n): { value: string; end: number } | undefined {\n if (source[start] === '`') {\n const closingQuote = source.indexOf('`', start + 1);\n return closingQuote === -1\n ? undefined\n : { value: source.slice(start + 1, closingQuote), end: closingQuote + 1 };\n }\n if (source[start] !== '\"') {\n return undefined;\n }\n let current = start + 1;\n while (current < source.length) {\n if (source[current] === '\\\\') {\n current += 2;\n continue;\n }\n if (source[current] === '\"') {\n const raw = source.slice(start, current + 1);\n try {\n return { value: JSON.parse(raw) as string, end: current + 1 };\n } catch {\n return undefined;\n }\n }\n current += 1;\n }\n return undefined;\n}\n\n/**\n * Parse a static Rego reference with dotted or quoted bracket segments.\n *\n * @param source - Rego source\n * @param start - First identifier offset\n * @returns Parsed reference\n */\nfunction parseStaticRegoReference(source: string, start: number): StaticRegoReference | undefined {\n const first = readIdentifier(source, start);\n if (!first) {\n return undefined;\n }\n const segments = [first.identifier];\n let current = first.end;\n while (current < source.length) {\n const separator = skipWhitespace(source, current);\n if (source[separator] === '.') {\n const identifierStart = skipWhitespace(source, separator + 1);\n const identifier = readIdentifier(source, identifierStart);\n if (!identifier) {\n break;\n }\n segments.push(identifier.identifier);\n current = identifier.end;\n continue;\n }\n if (source[separator] === '[') {\n const stringStart = skipWhitespace(source, separator + 1);\n const value = readQuotedString(source, stringStart);\n if (!value) {\n break;\n }\n const closingBracket = skipWhitespace(source, value.end);\n if (source[closingBracket] !== ']') {\n break;\n }\n segments.push(value.value);\n current = closingBracket + 1;\n continue;\n }\n break;\n }\n return {\n source: source.slice(start, current),\n segments,\n end: current,\n };\n}\n\n/**\n * Read the package reference declared by Rego source.\n *\n * @param contents - Rego source\n * @returns Package reference, or undefined when no declaration is present\n */\nexport function parseRegoPackageReference(\n contents: string,\n): Omit<StaticRegoReference, 'end'> | undefined {\n const declaration = PACKAGE_DECLARATION_PATTERN.exec(contents);\n if (!declaration) {\n return undefined;\n }\n const reference = parseStaticRegoReference(contents, declaration.index + declaration[0].length);\n return reference ? { source: reference.source, segments: reference.segments } : undefined;\n}\n\n/**\n * Skip a string literal while scanning executable Rego source.\n *\n * @param source - Rego source\n * @param start - Opening delimiter offset\n * @returns Offset immediately after the string\n */\nfunction skipStringLiteral(source: string, start: number): number {\n const delimiter = source[start]!;\n let current = start + 1;\n while (current < source.length) {\n if (delimiter === '\"' && source[current] === '\\\\') {\n current += 2;\n continue;\n }\n if (source[current] === delimiter) {\n return current + 1;\n }\n current += 1;\n }\n return current;\n}\n\n/**\n * Find executable static `data` references outside imports.\n *\n * @param contents - Rego source\n * @returns References with one-based source lines\n */\nexport function findDirectDataReferences(\n contents: string,\n): { reference: StaticRegoReference; line: number }[] {\n const references: { reference: StaticRegoReference; line: number }[] = [];\n let current = 0;\n while (current < contents.length) {\n if (contents[current] === '#') {\n const newline = contents.indexOf('\\n', current);\n current = newline === -1 ? contents.length : newline + 1;\n continue;\n }\n if (contents[current] === '\"' || contents[current] === '`') {\n current = skipStringLiteral(contents, current);\n continue;\n }\n const identifier = readIdentifier(contents, current);\n if (!identifier) {\n current += 1;\n continue;\n }\n if (identifier.identifier !== 'data') {\n current = identifier.end;\n continue;\n }\n const reference = parseStaticRegoReference(contents, current);\n if (!reference || reference.segments.length === 1) {\n current = identifier.end;\n continue;\n }\n const lineStart = contents.lastIndexOf('\\n', current - 1) + 1;\n if (!/^\\s*import\\s+$/u.test(contents.slice(lineStart, current))) {\n references.push({\n reference,\n line: contents.slice(0, current).split('\\n').length,\n });\n }\n current = reference.end;\n }\n return references;\n}\n","import {\n POLICY_MANIFEST_FILENAME,\n POLICY_MANIFEST_TRANSCEND_METADATA_KEY,\n POLICY_TEMPLATE_NAMES,\n type PolicyTemplateName,\n} from './policy-scaffold-templates.js';\nimport { findDirectDataReferences, parseRegoPackageReference } from './rego-reference.js';\n\n/** Shape of the policy bundle `.manifest` accepted by Policy Engine. */\nexport interface PolicyBundleManifest {\n /** Roots of the bundle, such as `policy_engine/transcend`. */\n roots: string[];\n /**\n * Optional scaffold template from `metadata.transcend.io.template`.\n *\n * Authoring hint only — upload does not branch on it. Permissions API\n * still keys off the remote bundle name `permissions`.\n */\n template?: PolicyTemplateName;\n}\n\n/** In-memory Rego file used to validate a policy bundle. */\nexport interface PolicyBundleRegoFile {\n /** POSIX-style path relative to the policy bundle directory. */\n path: string;\n /** Rego source contents. */\n contents: string;\n}\n\n/** Validated publishable policy bundle contents. */\nexport interface ValidatedPolicyBundleContents {\n /** Parsed and validated bundle manifest. */\n manifest: PolicyBundleManifest;\n /** Publishable Rego paths, excluding local test modules. */\n publishableRegoPaths: string[];\n}\n\n/**\n * Returns whether a relative path is a publishable Rego policy file.\n *\n * OPA test files (`*_test.rego`) are local-only and excluded from uploads.\n *\n * @param relativePath - Path relative to the bundle directory\n * @returns Whether the file belongs in the uploaded archive\n */\nexport function isPublishableRegoFile(relativePath: string): boolean {\n return relativePath.endsWith('.rego') && !relativePath.endsWith('_test.rego');\n}\n\n/**\n * Parse and validate the Policy Engine bundle manifest.\n *\n * @param contents - Raw manifest contents, or undefined when absent\n * @returns Parsed manifest\n */\nexport function parsePolicyBundleManifest(contents: string | undefined): PolicyBundleManifest {\n if (contents === undefined) {\n throw new Error(`Policy bundle directory must contain a ${POLICY_MANIFEST_FILENAME} file.`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(contents);\n } catch (error) {\n throw new Error(\n `${POLICY_MANIFEST_FILENAME} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error(`${POLICY_MANIFEST_FILENAME} must contain a JSON object.`);\n }\n\n const roots = (parsed as { roots?: unknown }).roots;\n if (!Array.isArray(roots) || roots.length === 0) {\n throw new Error(\n `${POLICY_MANIFEST_FILENAME} must declare \"roots\" as a non-empty array of strings (e.g. {\"roots\":[\"policy_engine\"]}).`,\n );\n }\n if (!roots.every((root) => typeof root === 'string' && root.length > 0)) {\n throw new Error(`${POLICY_MANIFEST_FILENAME} \"roots\" must be an array of non-empty strings.`);\n }\n if (\n !roots.every((root) =>\n root.split('/').every((segment: string) => /^[A-Za-z_][A-Za-z0-9_]*$/u.test(segment)),\n )\n ) {\n throw new Error(\n `${POLICY_MANIFEST_FILENAME} \"roots\" must contain slash-separated Rego identifiers so Policy Engine can re-namespace them safely.`,\n );\n }\n\n const template = parseOptionalManifestTemplate(parsed);\n\n return template === undefined ? { roots } : { roots, template };\n}\n\n/**\n * Read an optional `metadata.transcend.io.template` authoring hint.\n *\n * Invalid or unknown values are ignored so upload validation stays focused on\n * roots and Rego coverage.\n *\n * @param parsed - Parsed `.manifest` object\n * @returns Known template name, or undefined\n */\nfunction parseOptionalManifestTemplate(parsed: object): PolicyTemplateName | undefined {\n const metadata = (parsed as { metadata?: unknown }).metadata;\n if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {\n return undefined;\n }\n const transcend = (metadata as Record<string, unknown>)[POLICY_MANIFEST_TRANSCEND_METADATA_KEY];\n if (!transcend || typeof transcend !== 'object' || Array.isArray(transcend)) {\n return undefined;\n }\n const template = (transcend as { template?: unknown }).template;\n if (typeof template !== 'string') {\n return undefined;\n }\n return POLICY_TEMPLATE_NAMES.includes(template as PolicyTemplateName)\n ? (template as PolicyTemplateName)\n : undefined;\n}\n\n/**\n * Determine whether one document path starts with another.\n *\n * @param segments - Complete document path\n * @param prefix - Candidate root\n * @returns Whether the root covers the document path\n */\nfunction hasSegmentPrefix(segments: readonly string[], prefix: readonly string[]): boolean {\n return (\n segments.length >= prefix.length &&\n prefix.every((segment, index) => segment === segments[index])\n );\n}\n\n/**\n * Match the package syntax supported by Policy Engine activation.\n *\n * Bracketed segments remain supported below a declared dotted root.\n *\n * @param reference - Parsed package reference\n * @param root - Declared manifest root\n * @returns Whether activation can re-namespace the package\n */\nfunction rootCoversPackage(reference: { source: string }, root: readonly string[]): boolean {\n const dottedRoot = root.join('.');\n const suffix = reference.source.slice(dottedRoot.length);\n return (\n reference.source.startsWith(dottedRoot) &&\n (suffix === '' || suffix.startsWith('.') || /^\\s*\\[/u.test(suffix))\n );\n}\n\n/**\n * Determine whether `data.<root>` is referenced directly.\n *\n * @param segments - Parsed `data` reference segments\n * @param root - Declared manifest root\n * @returns Whether the reference targets the declared root\n */\nfunction referencesDeclaredRoot(segments: readonly string[], root: readonly string[]): boolean {\n return hasSegmentPrefix(segments.slice(1), root);\n}\n\n/**\n * Validate the manifest, publishable file requirement, and root coverage.\n *\n * This is the shared pure upload contract used by both `policy lint` and\n * `policy publish`.\n *\n * @param manifestContents - Raw manifest contents, or undefined when absent\n * @param regoFiles - All Rego files in the policy directory\n * @returns Validated manifest and publishable paths\n */\nexport function validatePolicyBundleContents(\n manifestContents: string | undefined,\n regoFiles: readonly PolicyBundleRegoFile[],\n): ValidatedPolicyBundleContents {\n const manifest = parsePolicyBundleManifest(manifestContents);\n const renderedRoots = manifest.roots.join(', ');\n const publishableFiles = regoFiles\n .filter(({ path }) => isPublishableRegoFile(path))\n .slice()\n .sort((left, right) => left.path.localeCompare(right.path));\n if (publishableFiles.length === 0) {\n throw new Error('Policy bundle directory must contain at least one .rego policy file.');\n }\n\n const rootPrefixes = manifest.roots.map((root) => root.split('/'));\n const uncovered = publishableFiles.flatMap(({ path, contents }) => {\n const packageReference = parseRegoPackageReference(contents);\n if (\n !packageReference ||\n rootPrefixes.some((prefix) => rootCoversPackage(packageReference, prefix))\n ) {\n return [];\n }\n return [\n ` - ${path} (package ${packageReference.source}) is not covered by roots [${renderedRoots}]`,\n ];\n });\n if (uncovered.length > 0) {\n throw new Error(\n [\n `${POLICY_MANIFEST_FILENAME} \"roots\" do not cover all Rego packages in the bundle; ` +\n 'uncovered packages will fail-closed at decide time. Either broaden \"roots\" or move the policy under a covered package:',\n ...uncovered,\n ].join('\\n'),\n );\n }\n\n const unsupportedReferences = publishableFiles.flatMap(({ path, contents }) =>\n findDirectDataReferences(contents)\n .filter(({ reference }) =>\n rootPrefixes.some((prefix) => referencesDeclaredRoot(reference.segments, prefix)),\n )\n .map(\n ({ reference, line }) =>\n ` - ${path}:${line} references ${reference.source}; import that package and use its local name`,\n ),\n );\n if (unsupportedReferences.length > 0) {\n throw new Error(\n [\n 'Publishable Rego must not reference its declared bundle roots directly because Policy Engine re-namespaces package imports during activation:',\n ...unsupportedReferences,\n ].join('\\n'),\n );\n }\n\n return {\n manifest,\n publishableRegoPaths: publishableFiles.map(({ path }) => path),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"policy-bundle-manifest-B1Herd_n.mjs","names":[],"sources":["../src/lib/policy/rego-reference.ts","../src/lib/policy/policy-bundle-manifest.ts"],"sourcesContent":["/** Parsed static Rego reference. */\ninterface StaticRegoReference {\n /** Source representation of the reference. */\n source: string;\n /** Logical document path segments. */\n segments: string[];\n /** Offset immediately after the reference. */\n end: number;\n}\n\n/** Matches the beginning of a Rego package declaration. */\nconst PACKAGE_DECLARATION_PATTERN = /^\\s*package\\s+/mu;\n\n/**\n * Read an identifier at a source offset.\n *\n * @param source - Rego source\n * @param start - Candidate identifier offset\n * @returns Identifier and following offset\n */\nfunction readIdentifier(\n source: string,\n start: number,\n): { identifier: string; end: number } | undefined {\n const match = /^[A-Za-z_][A-Za-z0-9_]*/u.exec(source.slice(start));\n return match ? { identifier: match[0], end: start + match[0].length } : undefined;\n}\n\n/**\n * Advance past Rego whitespace.\n *\n * @param source - Rego source\n * @param start - Initial source offset\n * @returns First non-whitespace offset\n */\nfunction skipWhitespace(source: string, start: number): number {\n let current = start;\n while (current < source.length && /\\s/u.test(source[current]!)) {\n current += 1;\n }\n return current;\n}\n\n/**\n * Read a quoted or raw Rego string.\n *\n * @param source - Rego source\n * @param start - Opening quote offset\n * @returns Decoded string and following offset\n */\nfunction readQuotedString(\n source: string,\n start: number,\n): { value: string; end: number } | undefined {\n if (source[start] === '`') {\n const closingQuote = source.indexOf('`', start + 1);\n return closingQuote === -1\n ? undefined\n : { value: source.slice(start + 1, closingQuote), end: closingQuote + 1 };\n }\n if (source[start] !== '\"') {\n return undefined;\n }\n let current = start + 1;\n while (current < source.length) {\n if (source[current] === '\\\\') {\n current += 2;\n continue;\n }\n if (source[current] === '\"') {\n const raw = source.slice(start, current + 1);\n try {\n return { value: JSON.parse(raw) as string, end: current + 1 };\n } catch {\n return undefined;\n }\n }\n current += 1;\n }\n return undefined;\n}\n\n/**\n * Parse a static Rego reference with dotted or quoted bracket segments.\n *\n * @param source - Rego source\n * @param start - First identifier offset\n * @returns Parsed reference\n */\nfunction parseStaticRegoReference(source: string, start: number): StaticRegoReference | undefined {\n const first = readIdentifier(source, start);\n if (!first) {\n return undefined;\n }\n const segments = [first.identifier];\n let current = first.end;\n while (current < source.length) {\n const separator = skipWhitespace(source, current);\n if (source[separator] === '.') {\n const identifierStart = skipWhitespace(source, separator + 1);\n const identifier = readIdentifier(source, identifierStart);\n if (!identifier) {\n break;\n }\n segments.push(identifier.identifier);\n current = identifier.end;\n continue;\n }\n if (source[separator] === '[') {\n const stringStart = skipWhitespace(source, separator + 1);\n const value = readQuotedString(source, stringStart);\n if (!value) {\n break;\n }\n const closingBracket = skipWhitespace(source, value.end);\n if (source[closingBracket] !== ']') {\n break;\n }\n segments.push(value.value);\n current = closingBracket + 1;\n continue;\n }\n break;\n }\n return {\n source: source.slice(start, current),\n segments,\n end: current,\n };\n}\n\n/**\n * Read the package reference declared by Rego source.\n *\n * @param contents - Rego source\n * @returns Package reference, or undefined when no declaration is present\n */\nexport function parseRegoPackageReference(\n contents: string,\n): Omit<StaticRegoReference, 'end'> | undefined {\n const declaration = PACKAGE_DECLARATION_PATTERN.exec(contents);\n if (!declaration) {\n return undefined;\n }\n const reference = parseStaticRegoReference(contents, declaration.index + declaration[0].length);\n return reference ? { source: reference.source, segments: reference.segments } : undefined;\n}\n\n/**\n * Skip a string literal while scanning executable Rego source.\n *\n * @param source - Rego source\n * @param start - Opening delimiter offset\n * @returns Offset immediately after the string\n */\nfunction skipStringLiteral(source: string, start: number): number {\n const delimiter = source[start]!;\n let current = start + 1;\n while (current < source.length) {\n if (delimiter === '\"' && source[current] === '\\\\') {\n current += 2;\n continue;\n }\n if (source[current] === delimiter) {\n return current + 1;\n }\n current += 1;\n }\n return current;\n}\n\n/**\n * Find executable static `data` references outside imports.\n *\n * @param contents - Rego source\n * @returns References with one-based source lines\n */\nexport function findDirectDataReferences(\n contents: string,\n): { reference: StaticRegoReference; line: number }[] {\n const references: { reference: StaticRegoReference; line: number }[] = [];\n let current = 0;\n while (current < contents.length) {\n if (contents[current] === '#') {\n const newline = contents.indexOf('\\n', current);\n current = newline === -1 ? contents.length : newline + 1;\n continue;\n }\n if (contents[current] === '\"' || contents[current] === '`') {\n current = skipStringLiteral(contents, current);\n continue;\n }\n const identifier = readIdentifier(contents, current);\n if (!identifier) {\n current += 1;\n continue;\n }\n if (identifier.identifier !== 'data') {\n current = identifier.end;\n continue;\n }\n const reference = parseStaticRegoReference(contents, current);\n if (!reference || reference.segments.length === 1) {\n current = identifier.end;\n continue;\n }\n const lineStart = contents.lastIndexOf('\\n', current - 1) + 1;\n if (!/^\\s*import\\s+$/u.test(contents.slice(lineStart, current))) {\n references.push({\n reference,\n line: contents.slice(0, current).split('\\n').length,\n });\n }\n current = reference.end;\n }\n return references;\n}\n","import {\n POLICY_MANIFEST_FILENAME,\n POLICY_MANIFEST_TRANSCEND_METADATA_KEY,\n POLICY_TEMPLATE_NAMES,\n type PolicyTemplateName,\n} from './policy-scaffold-templates.js';\nimport { findDirectDataReferences, parseRegoPackageReference } from './rego-reference.js';\n\n/** Shape of the policy bundle `.manifest` accepted by Policy Engine. */\nexport interface PolicyBundleManifest {\n /** Roots of the bundle, such as `policy_engine/transcend`. */\n roots: string[];\n /**\n * Optional scaffold template from `metadata.transcend.io.template`.\n *\n * Authoring hint only — upload does not branch on it. Permissions API\n * still keys off the remote bundle name `permissions`.\n */\n template?: PolicyTemplateName;\n}\n\n/** In-memory Rego file used to validate a policy bundle. */\nexport interface PolicyBundleRegoFile {\n /** POSIX-style path relative to the policy bundle directory. */\n path: string;\n /** Rego source contents. */\n contents: string;\n}\n\n/** Validated publishable policy bundle contents. */\nexport interface ValidatedPolicyBundleContents {\n /** Parsed and validated bundle manifest. */\n manifest: PolicyBundleManifest;\n /** Publishable Rego paths, excluding local test modules. */\n publishableRegoPaths: string[];\n}\n\n/**\n * Returns whether a relative path is a publishable Rego policy file.\n *\n * OPA test files (`*_test.rego`) are local-only and excluded from uploads.\n *\n * @param relativePath - Path relative to the bundle directory\n * @returns Whether the file belongs in the uploaded archive\n */\nexport function isPublishableRegoFile(relativePath: string): boolean {\n return relativePath.endsWith('.rego') && !relativePath.endsWith('_test.rego');\n}\n\n/**\n * Parse and validate the Policy Engine bundle manifest.\n *\n * @param contents - Raw manifest contents, or undefined when absent\n * @returns Parsed manifest\n */\nexport function parsePolicyBundleManifest(contents: string | undefined): PolicyBundleManifest {\n if (contents === undefined) {\n throw new Error(`Policy bundle directory must contain a ${POLICY_MANIFEST_FILENAME} file.`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(contents);\n } catch (error) {\n throw new Error(\n `${POLICY_MANIFEST_FILENAME} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error(`${POLICY_MANIFEST_FILENAME} must contain a JSON object.`);\n }\n\n const roots = (parsed as { roots?: unknown }).roots;\n if (!Array.isArray(roots) || roots.length === 0) {\n throw new Error(\n `${POLICY_MANIFEST_FILENAME} must declare \"roots\" as a non-empty array of strings (e.g. {\"roots\":[\"policy_engine\"]}).`,\n );\n }\n if (!roots.every((root) => typeof root === 'string' && root.length > 0)) {\n throw new Error(`${POLICY_MANIFEST_FILENAME} \"roots\" must be an array of non-empty strings.`);\n }\n if (\n !roots.every((root) =>\n root.split('/').every((segment: string) => /^[A-Za-z_][A-Za-z0-9_]*$/u.test(segment)),\n )\n ) {\n throw new Error(\n `${POLICY_MANIFEST_FILENAME} \"roots\" must contain slash-separated Rego identifiers so Policy Engine can re-namespace them safely.`,\n );\n }\n\n const template = parseOptionalManifestTemplate(parsed);\n\n return template === undefined ? { roots } : { roots, template };\n}\n\n/**\n * Read an optional `metadata.transcend.io.template` authoring hint.\n *\n * Invalid or unknown values are ignored so upload validation stays focused on\n * roots and Rego coverage.\n *\n * @param parsed - Parsed `.manifest` object\n * @returns Known template name, or undefined\n */\nfunction parseOptionalManifestTemplate(parsed: object): PolicyTemplateName | undefined {\n const metadata = (parsed as { metadata?: unknown }).metadata;\n if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {\n return undefined;\n }\n const transcend = (metadata as Record<string, unknown>)[POLICY_MANIFEST_TRANSCEND_METADATA_KEY];\n if (!transcend || typeof transcend !== 'object' || Array.isArray(transcend)) {\n return undefined;\n }\n const template = (transcend as { template?: unknown }).template;\n if (typeof template !== 'string') {\n return undefined;\n }\n return POLICY_TEMPLATE_NAMES.includes(template as PolicyTemplateName)\n ? (template as PolicyTemplateName)\n : undefined;\n}\n\n/**\n * Determine whether one document path starts with another.\n *\n * @param segments - Complete document path\n * @param prefix - Candidate root\n * @returns Whether the root covers the document path\n */\nfunction hasSegmentPrefix(segments: readonly string[], prefix: readonly string[]): boolean {\n return (\n segments.length >= prefix.length &&\n prefix.every((segment, index) => segment === segments[index])\n );\n}\n\n/**\n * Match the package syntax supported by Policy Engine activation.\n *\n * Bracketed segments remain supported below a declared dotted root.\n *\n * @param reference - Parsed package reference\n * @param root - Declared manifest root\n * @returns Whether activation can re-namespace the package\n */\nfunction rootCoversPackage(reference: { source: string }, root: readonly string[]): boolean {\n const dottedRoot = root.join('.');\n const suffix = reference.source.slice(dottedRoot.length);\n return (\n reference.source.startsWith(dottedRoot) &&\n (suffix === '' || suffix.startsWith('.') || /^\\s*\\[/u.test(suffix))\n );\n}\n\n/**\n * Determine whether `data.<root>` is referenced directly.\n *\n * @param segments - Parsed `data` reference segments\n * @param root - Declared manifest root\n * @returns Whether the reference targets the declared root\n */\nfunction referencesDeclaredRoot(segments: readonly string[], root: readonly string[]): boolean {\n return hasSegmentPrefix(segments.slice(1), root);\n}\n\n/**\n * Validate the manifest, publishable file requirement, and root coverage.\n *\n * This is the shared pure upload contract used by both `policy lint` and\n * `policy publish`.\n *\n * @param manifestContents - Raw manifest contents, or undefined when absent\n * @param regoFiles - All Rego files in the policy directory\n * @returns Validated manifest and publishable paths\n */\nexport function validatePolicyBundleContents(\n manifestContents: string | undefined,\n regoFiles: readonly PolicyBundleRegoFile[],\n): ValidatedPolicyBundleContents {\n const manifest = parsePolicyBundleManifest(manifestContents);\n const renderedRoots = manifest.roots.join(', ');\n const publishableFiles = regoFiles\n .filter(({ path }) => isPublishableRegoFile(path))\n .slice()\n .sort((left, right) => left.path.localeCompare(right.path));\n if (publishableFiles.length === 0) {\n throw new Error('Policy bundle directory must contain at least one .rego policy file.');\n }\n\n const rootPrefixes = manifest.roots.map((root) => root.split('/'));\n const uncovered = publishableFiles.flatMap(({ path, contents }) => {\n const packageReference = parseRegoPackageReference(contents);\n if (\n !packageReference ||\n rootPrefixes.some((prefix) => rootCoversPackage(packageReference, prefix))\n ) {\n return [];\n }\n return [\n ` - ${path} (package ${packageReference.source}) is not covered by roots [${renderedRoots}]`,\n ];\n });\n if (uncovered.length > 0) {\n throw new Error(\n [\n `${POLICY_MANIFEST_FILENAME} \"roots\" do not cover all Rego packages in the bundle; ` +\n 'uncovered packages will fail-closed at decide time. Either broaden \"roots\" or move the policy under a covered package:',\n ...uncovered,\n ].join('\\n'),\n );\n }\n\n const unsupportedReferences = publishableFiles.flatMap(({ path, contents }) =>\n findDirectDataReferences(contents)\n .filter(({ reference }) =>\n rootPrefixes.some((prefix) => referencesDeclaredRoot(reference.segments, prefix)),\n )\n .map(\n ({ reference, line }) =>\n ` - ${path}:${line} references ${reference.source}; import that package and use its local name`,\n ),\n );\n if (unsupportedReferences.length > 0) {\n throw new Error(\n [\n 'Publishable Rego must not reference its declared bundle roots directly because Policy Engine re-namespaces package imports during activation:',\n ...unsupportedReferences,\n ].join('\\n'),\n );\n }\n\n return {\n manifest,\n publishableRegoPaths: publishableFiles.map(({ path }) => path),\n };\n}\n"],"mappings":"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"}
|
package/dist/{policy-project-discovery-DV5PnFJq.mjs → policy-project-discovery-oc49b2gr.mjs}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{g as e}from"./constants-
|
|
1
|
+
import{g as e}from"./constants-boymUaFC.mjs";import{n as t,r as n}from"./project-discovery-CWRjlFnC.mjs";import{join as r,resolve as i}from"node:path";import a from"js-yaml";var o=`https://raw.githubusercontent.com/transcend-io/tools/main/packages/cli/schema/permissions-policy-input.json`,s=[{preferences:[{name:`Analytics`,choice:!0,days_since_choice:42},{name:`SaleOfInfo`,choice:null}],context:{region:`US-CA`}}],c={$schema:`http://json-schema.org/draft-07/schema#`,$id:o,title:`Permissions API policy input`,description:"JSON document Sombra posts as `{ input }` when evaluating a Permissions policy bundle via the OPA Data API.",type:`object`,additionalProperties:!1,required:[`preferences`,`context`],properties:{preferences:{type:`array`,description:`Purpose preference rows from Preference Store for the current subject.`,items:{type:`object`,additionalProperties:!1,required:[`name`,`choice`],properties:{name:{type:`string`,description:`Purpose slug (for example Analytics or SaleOfInfo).`},choice:{type:[`boolean`,`null`],description:`true when opted in, false when opted out, or null when unset.`},days_since_choice:{type:`integer`,minimum:0,description:`Whole days since the preference was last set. Omitted when no timestamp is available.`}}}},context:{type:`object`,description:`Caller-supplied context for the decision (for example region). Use an empty object when none is provided.`,additionalProperties:!0}},examples:s};const l=o,u=`1.18.2`,d=`example`,f=`${d}-bundle`,p=`input.schema.json`,m=`.manifest`,h=`permissions`,g=`transcend.io`,_={Generic:`generic`,Permissions:`permissions`},v=[_.Generic,_.Permissions];function y(t,n=_.Generic){return`{
|
|
2
2
|
"$schema": "https://openpolicyagent.org/schemas/bundle/v1/manifest.schema.json",
|
|
3
3
|
"revision": "",
|
|
4
4
|
"roots": ["${t}"],
|
|
@@ -427,4 +427,4 @@ package ${e}.purposes
|
|
|
427
427
|
import rego.v1
|
|
428
428
|
`,description:`Create the purposes entrypoint`},{path:`${n}/input.example.json`,contents:r,description:`Create a sanitized permissions evaluation input example`},{path:`${n}/input.json`,contents:r,description:`Create a local evaluation input (gitignored copy of the example)`},{path:`${n}/.gitignore`,contents:w,description:`Ignore only the private local input file`}]}function I(e,t,n=A(t)){switch(e){case _.Generic:return M(t,n);case _.Permissions:return F(t,n);default:throw Error(`Unknown policy template: ${String(e)}`)}}function L(e){let{bundleDirectory:t,root:n,template:r}=e,i=r===_.Permissions?`data.${n}.purposes`:`data.${n}.result`;return[`transcend policy eval ${t} \\`,` --package=${i} \\`,` --input=${t}/input.json \\`,` --schema=${t}/${p}`].join(`
|
|
429
429
|
`)}const R=`transcend/policy`;function z(e,t=R){return i(e,t)}function B(e,t){if(!e.fs.existsSync(t)||!e.fs.statSync(t).isDirectory())return[];let n=r(t,m);return e.fs.existsSync(n)&&e.fs.statSync(n).isFile()?[t]:e.fs.readdirSync(t,{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>r(t,e.name)).filter(t=>{let n=r(t,m);return e.fs.existsSync(n)&&e.fs.statSync(n).isFile()}).sort((e,t)=>e.localeCompare(t))}function V(e,r=R){let i=e.process.cwd(),a=z(i,r);if(e.fs.existsSync(a)&&!e.fs.statSync(a).isDirectory())throw Error(`Policy target is not a directory: ${a}`);return{invocationDirectory:i,targetDirectory:a,...n(e,{invocationDirectory:i,targetDirectory:a,standaloneProjectRoot:i}),relativePaths:t(e,a),relativeFilePaths:t(e,a,{includeDirectories:!1})}}export{j as S,A as _,h as a,D as b,m as c,u as d,d as f,_ as g,O as h,z as i,g as l,v as m,B as n,l as o,k as p,V as r,p as s,R as t,f as u,L as v,x,I as y};
|
|
430
|
-
//# sourceMappingURL=policy-project-discovery-
|
|
430
|
+
//# sourceMappingURL=policy-project-discovery-oc49b2gr.mjs.map
|
package/dist/{policy-project-discovery-DV5PnFJq.mjs.map → policy-project-discovery-oc49b2gr.mjs.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy-project-discovery-DV5PnFJq.mjs","names":["permissionsPolicyInputSchema.$id","CLI_VERSION","permissionsPolicyInputSchema","permissionsPolicyInputSchema.examples"],"sources":["../schema/permissions-policy-input.json","../src/lib/policy/policy-scaffold-templates.ts","../src/lib/policy/policy-project-discovery.ts"],"sourcesContent":["","import yaml from 'js-yaml';\n\nimport permissionsPolicyInputSchema from '../../../schema/permissions-policy-input.json' with { type: 'json' };\nimport { version as CLI_VERSION } from '../../constants.js';\n\n/** Published Permissions API OPA input schema `$id` (raw GitHub URL). */\nexport const PERMISSIONS_POLICY_INPUT_SCHEMA_ID = permissionsPolicyInputSchema.$id;\n\n/** OPA version targeted by generated policy authoring configuration. */\nexport const POLICY_STARTER_OPA_VERSION = '1.18.2';\n\n/**\n * Package-path prefix for the disposable starter bundle.\n *\n * Matches OPA `.manifest` `roots` and Regal `project.roots` (not a filesystem\n * folder name by itself). The default publish directory is `{root}-bundle/`;\n * `policy new --bundle-dir` can override that basename.\n */\nexport const POLICY_STARTER_ROOT = 'example';\n\n/** Publish directory name for the disposable starter (`{root}-bundle`). */\nexport const POLICY_STARTER_BUNDLE_DIRECTORY = `${POLICY_STARTER_ROOT}-bundle`;\n\n/** Input JSON Schema filename written beside each publish directory's fixtures. */\nexport const POLICY_INPUT_SCHEMA_FILENAME = 'input.schema.json';\n\n/** One deterministic file in the safe policy starter. */\nexport interface PolicyStarterFile {\n /** POSIX-style path relative to the policy workspace directory. */\n path: string;\n /** Complete UTF-8 file contents. */\n contents: string;\n /** Human-readable reason shown in the plan. */\n description: string;\n}\n\n/** OPA bundle manifest filename used for local authoring and upload. */\nexport const POLICY_MANIFEST_FILENAME = '.manifest';\n\n/**\n * Remote bundle name reserved for Permissions API.\n *\n * Upload treats every bundle the same; Sombra's Permissions API always queries\n * this fixed name (never taken from the request body).\n */\nexport const PERMISSIONS_POLICY_BUNDLE_NAME = 'permissions';\n\n/** OPA `.manifest` `metadata` key for Transcend authoring hints. */\nexport const POLICY_MANIFEST_TRANSCEND_METADATA_KEY = 'transcend.io';\n\n/** Supported policy bundle templates (enum-style constants). */\nexport const PolicyTemplate = {\n Generic: 'generic',\n Permissions: 'permissions',\n} as const;\n\n/** Supported policy bundle template names. */\nexport const POLICY_TEMPLATE_NAMES = [PolicyTemplate.Generic, PolicyTemplate.Permissions] as const;\n\n/** A supported policy bundle template name. */\nexport type PolicyTemplateName = (typeof PolicyTemplate)[keyof typeof PolicyTemplate];\n\n/**\n * OPA `.manifest` `metadata.transcend.io` key for the CLI version that\n * generated the scaffold template.\n */\nexport const POLICY_MANIFEST_TEMPLATE_VERSION_KEY = 'templateVersion';\n\n/**\n * Build an OPA bundle manifest for a given root and scaffold template.\n *\n * `metadata.transcend.io.template` and `templateVersion` are authoring hints\n * only — Policy Engine upload does not branch on them. Permissions API still\n * keys off the remote bundle name {@link PERMISSIONS_POLICY_BUNDLE_NAME}.\n * `templateVersion` records the `@transcend-io/cli` package version used to\n * generate the scaffold.\n *\n * @param root - Package root\n * @param template - Scaffold template that produced this bundle\n * @returns Manifest JSON\n */\nexport function buildPolicyManifestTemplate(\n root: string,\n template: PolicyTemplateName = PolicyTemplate.Generic,\n): string {\n return `{\n \"$schema\": \"https://openpolicyagent.org/schemas/bundle/v1/manifest.schema.json\",\n \"revision\": \"\",\n \"roots\": [\"${root}\"],\n \"rego_version\": 1,\n \"metadata\": {\n \"${POLICY_MANIFEST_TRANSCEND_METADATA_KEY}\": {\n \"template\": \"${template}\",\n \"${POLICY_MANIFEST_TEMPLATE_VERSION_KEY}\": \"${CLI_VERSION}\"\n }\n }\n}\n`;\n}\n\n/** OPA bundle manifest for the disposable starter publish directory. */\nexport const POLICY_MANIFEST_TEMPLATE = buildPolicyManifestTemplate(\n POLICY_STARTER_ROOT,\n PolicyTemplate.Generic,\n);\n\n/**\n * Build workspace-level Regal configuration for given roots.\n *\n * `project.roots` are package-path prefixes (same meaning as OPA `.manifest`\n * roots), not filesystem folders.\n *\n * @param roots - Package-path roots\n * @returns YAML configuration\n */\nexport function buildPolicyRegalConfigTemplate(roots: readonly string[]): string {\n const rootsSection =\n roots.length === 0\n ? ' roots: []'\n : ` roots:\\n${roots.map((root) => ` - ${root}`).join('\\n')}`;\n return `# Workspace-level Regal config for both publishable bundles under this tree.\n# \\`project.roots\\` are package-path prefixes (same meaning as OPA \\`.manifest\\`\n# roots), not filesystem folders. Each bundle also has its own \\`.manifest\\`.\ncapabilities:\n from:\n engine: opa\n version: v${POLICY_STARTER_OPA_VERSION}\nproject:\n${rootsSection}\n rego-version: 1\n`;\n}\n\n/**\n * Merge one package-path root into an existing Regal config without wiping\n * unrelated keys (custom rules, ignores, etc.).\n *\n * Comments and key order may be reformatted by the YAML serializer. Invalid\n * `project.roots` shapes throw instead of silently dropping prior roots.\n *\n * @param existingContents - Current `.regal/config.yaml` contents\n * @param rootToAdd - Package-path root to include\n * @returns Updated file contents and the full roots list\n */\nexport function mergePolicyRegalConfigRoots(\n existingContents: string,\n rootToAdd: string,\n): { contents: string; roots: string[] } {\n const trimmed = existingContents.trim();\n if (trimmed.length === 0) {\n const roots = [rootToAdd];\n return { contents: buildPolicyRegalConfigTemplate(roots), roots };\n }\n\n let parsed: unknown;\n try {\n parsed = yaml.load(existingContents);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n throw new Error(`.regal/config.yaml is not valid YAML: ${detail}`);\n }\n\n if (parsed === null || parsed === undefined) {\n const roots = [rootToAdd];\n return { contents: buildPolicyRegalConfigTemplate(roots), roots };\n }\n if (typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error('.regal/config.yaml must be a YAML mapping at the top level.');\n }\n\n const config = { ...(parsed as Record<string, unknown>) };\n const existingProject = config.project;\n if (\n existingProject !== undefined &&\n existingProject !== null &&\n (typeof existingProject !== 'object' || Array.isArray(existingProject))\n ) {\n throw new Error('.regal/config.yaml project must be a YAML mapping when present.');\n }\n\n const project: Record<string, unknown> = {\n ...((existingProject as Record<string, unknown> | undefined) ?? {}),\n };\n const existingRoots = project.roots;\n let roots: string[];\n if (existingRoots === undefined) {\n roots = [rootToAdd];\n } else if (\n Array.isArray(existingRoots) &&\n existingRoots.every((root): root is string => typeof root === 'string')\n ) {\n roots = [...new Set([...existingRoots, rootToAdd])].sort((left, right) =>\n left.localeCompare(right),\n );\n } else {\n throw new Error(\n '.regal/config.yaml project.roots must be an array of strings. ' +\n 'Fix the Regal config before running `transcend policy new`.',\n );\n }\n\n project.roots = roots;\n if (project['rego-version'] === undefined) {\n project['rego-version'] = 1;\n }\n config.project = project;\n\n if (config.capabilities === undefined) {\n config.capabilities = {\n from: {\n engine: 'opa',\n version: `v${POLICY_STARTER_OPA_VERSION}`,\n },\n };\n }\n\n const dumped = yaml.dump(config, {\n lineWidth: -1,\n noRefs: true,\n sortKeys: false,\n });\n return {\n contents: dumped.endsWith('\\n') ? dumped : `${dumped}\\n`,\n roots,\n };\n}\n\n/**\n * Workspace-level Regal configuration.\n *\n * `project.roots` are package-path prefixes (same meaning as OPA `.manifest`\n * roots), not filesystem folders.\n */\nexport const POLICY_REGAL_CONFIG_TEMPLATE = buildPolicyRegalConfigTemplate([POLICY_STARTER_ROOT]);\n\n/** Regal config for an empty workspace with no bundles yet. */\nexport const POLICY_REGAL_CONFIG_EMPTY_TEMPLATE = buildPolicyRegalConfigTemplate([]);\n\n/** Disposable, fail-closed example policy. */\nexport const POLICY_RESULT_REGO_TEMPLATE = `# METADATA\n# title: Disposable example result\n# description: |\n# A fail-closed teaching entrypoint. Replace this example with the document\n# tree and result contract required by your application.\n# scope: package\n# entrypoint: true\npackage ${POLICY_STARTER_ROOT}.result\n\nimport rego.v1\n\ndefault decision := \"deny\"\n\ndecision := \"allow\" if {\n\tinput.subject.trusted == true\n}\n\ndefault reason_code := \"untrusted_or_invalid_input\"\n\nreason_code := \"trusted_subject\" if {\n\tdecision == \"allow\"\n}\n`;\n\n/** Executable tests adjacent to the disposable example policy. */\nexport const POLICY_RESULT_TEST_REGO_TEMPLATE = `package ${POLICY_STARTER_ROOT}.result_test\n\nimport data.${POLICY_STARTER_ROOT}.result\nimport rego.v1\n\ntest_denies_untrusted_subject if {\n\tresult.decision == \"deny\" with input as {\"subject\": {\"trusted\": false}}\n\tresult.reason_code == \"untrusted_or_invalid_input\" with input as {\"subject\": {\"trusted\": false}}\n}\n\ntest_fails_closed_for_missing_subject if {\n\tresult.decision == \"deny\" with input as {}\n\tresult.reason_code == \"untrusted_or_invalid_input\" with input as {}\n}\n\ntest_allows_trusted_subject if {\n\tresult.decision == \"allow\" with input as {\"subject\": {\"trusted\": true}}\n\tresult.reason_code == \"trusted_subject\" with input as {\"subject\": {\"trusted\": true}}\n}\n`;\n\n/** Sanitized local input example containing no credentials or personal data. */\nexport const POLICY_INPUT_EXAMPLE_TEMPLATE = `{\n \"subject\": {\n \"trusted\": false\n }\n}\n`;\n\n/** Input JSON Schema for the disposable starter (editor + \\`opa check -s\\`). */\nexport const POLICY_INPUT_SCHEMA_TEMPLATE = `{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": \"https://transcend.io/policy-schemas/${POLICY_STARTER_ROOT}/input.json\",\n \"title\": \"Example policy input\",\n \"description\": \"Disposable teaching envelope for the example bundle.\",\n \"type\": \"object\",\n \"additionalProperties\": true,\n \"properties\": {\n \"subject\": {\n \"type\": \"object\",\n \"additionalProperties\": true,\n \"properties\": {\n \"trusted\": {\n \"type\": \"boolean\",\n \"description\": \"When true, the example policy allows; otherwise it denies.\"\n }\n }\n }\n }\n}\n`;\n\n/** Target-local ignore rules for private evaluation input only. */\nexport const POLICY_GITIGNORE_TEMPLATE = `# Local policy evaluation input (VS Code / Regal look for input.json here).\n/input.json\n`;\n\n/** Concise workspace-local authoring guide (multi-bundle, empty workspace). */\nexport const POLICY_README_TEMPLATE = `# Transcend Policy\n\nMulti-bundle workspace for Transcend Policy Engine bundles\n(OPA / Rego v1). Requires **Transcend CLI ≥ 11** (\\`transcend policy --help\\`\nshould list \\`init\\`, \\`new\\`, and positional \\`[directory]\\` args).\n\nEach \\`*-bundle/\\` directory is a separate publishable unit (own \\`.manifest\\`,\ninput, and query path). Add a new bundle with:\n\n\\`\\`\\`sh\ntranscend policy new\n\\`\\`\\`\n\n## Quick start\n\n\\`\\`\\`sh\ntranscend policy init # create the workspace\ntranscend policy new --template generic # add a bundle from a template\n\ntranscend policy lint --noInteractive\ntranscend policy test\ntranscend policy eval transcend/policy/example-bundle \\\\\n --package=data.example.result \\\\\n --input=transcend/policy/example-bundle/input.json\n\\`\\`\\`\n\n\\`policy new\\` also writes a gitignored \\`input.json\\` (copy of \\`input.example.json\\`)\nfor local Evaluate. \\`policy lint\\` / \\`policy test\\` default to every child with a\n\\`.manifest\\` and use bundle-mode \\`opa test -b\\`, so both input files can coexist\nwithout a merge error.\n\n## Layout\n\n\\`\\`\\`\ntranscend/policy/\n example-bundle/ # publish directory (added by policy new)\n example/ # package root (matches .manifest roots)\n input.example.json\n input.json # local only (gitignored)\n input.schema.json # input JSON Schema (editor + opa --schema)\n .regal/config.yaml\n\\`\\`\\`\n\nReplace or delete example bundles when you have a real policy. Publish each\nbundle separately (\\`transcend policy publish --remote-bundle-name … <dir>\\`).\n\n## Permissions API\n\nScaffold the Permissions starter, then simulate the query Sombra runs:\n\n\\`\\`\\`sh\ntranscend policy new \\\\\n --template permissions \\\\\n --name ${PERMISSIONS_POLICY_BUNDLE_NAME} \\\\\n --bundle-dir ${PERMISSIONS_POLICY_BUNDLE_NAME}-bundle \\\\\n --yes\n\ntranscend policy eval transcend/policy/${PERMISSIONS_POLICY_BUNDLE_NAME}-bundle \\\\\n --package=data.${PERMISSIONS_POLICY_BUNDLE_NAME}.purposes \\\\\n --input=transcend/policy/${PERMISSIONS_POLICY_BUNDLE_NAME}-bundle/input.json \\\\\n --schema=transcend/policy/${PERMISSIONS_POLICY_BUNDLE_NAME}-bundle/input.schema.json\n\\`\\`\\`\n\nUpload does **not** distinguish bundle kinds. What makes a bundle “Permissions”\nis the remote name plus where Sombra queries it:\n\n- **Permissions API** always loads the fixed remote bundle name\n \\`${PERMISSIONS_POLICY_BUNDLE_NAME}\\`. Publish that starter with\n \\`--remote-bundle-name=${PERMISSIONS_POLICY_BUNDLE_NAME}\\`.\n- **Generic / decide** bundles use any other \\`--remote-bundle-name\\` and the decide\n path.\n\nScaffolded \\`.manifest\\` files may include \\`metadata.transcend.io.template\\`\n(\\`generic\\` or \\`permissions\\`) and \\`templateVersion\\` (the \\`@transcend-io/cli\\`\nversion that generated the scaffold) as authoring hints. Upload ignores them;\n\\`policy publish\\` may warn when the template hint and \\`--remote-bundle-name\\` disagree.\n`;\n\n/** Workspace-relative path to the disposable example entrypoint. */\nexport const POLICY_STARTER_RESULT_REGO_PATH = `${POLICY_STARTER_BUNDLE_DIRECTORY}/${POLICY_STARTER_ROOT}/result/result.rego`;\n\n/** Workspace-relative path to the disposable example tests. */\nexport const POLICY_STARTER_RESULT_TEST_REGO_PATH = `${POLICY_STARTER_BUNDLE_DIRECTORY}/${POLICY_STARTER_ROOT}/result/result_test.rego`;\n\n/** Complete safe starter in deterministic plan order. */\nconst POLICY_STARTER_FILES: readonly PolicyStarterFile[] = [\n {\n path: `${POLICY_STARTER_BUNDLE_DIRECTORY}/${POLICY_MANIFEST_FILENAME}`,\n contents: POLICY_MANIFEST_TEMPLATE,\n description: 'Create the OPA bundle .manifest',\n },\n {\n path: '.regal/config.yaml',\n contents: POLICY_REGAL_CONFIG_TEMPLATE,\n description: `Pin strict Rego v1 linting to OPA ${POLICY_STARTER_OPA_VERSION} capabilities`,\n },\n {\n path: `${POLICY_STARTER_BUNDLE_DIRECTORY}/${POLICY_INPUT_SCHEMA_FILENAME}`,\n contents: POLICY_INPUT_SCHEMA_TEMPLATE,\n description: 'Create the input JSON Schema for the example bundle',\n },\n {\n path: POLICY_STARTER_RESULT_REGO_PATH,\n contents: POLICY_RESULT_REGO_TEMPLATE,\n description: 'Create a documented fail-closed result entrypoint',\n },\n {\n path: POLICY_STARTER_RESULT_TEST_REGO_PATH,\n contents: POLICY_RESULT_TEST_REGO_TEMPLATE,\n description: 'Create executable tests for allow and fail-closed decisions',\n },\n {\n path: `${POLICY_STARTER_BUNDLE_DIRECTORY}/input.example.json`,\n contents: POLICY_INPUT_EXAMPLE_TEMPLATE,\n description: 'Create a sanitized local evaluation input',\n },\n {\n path: `${POLICY_STARTER_BUNDLE_DIRECTORY}/.gitignore`,\n contents: POLICY_GITIGNORE_TEMPLATE,\n description: 'Ignore only the private local input file',\n },\n {\n path: 'README.md',\n contents: POLICY_README_TEMPLATE,\n description: 'Create the concise policy project guide',\n },\n];\n\n/**\n * Generate an independent copy of the safe policy starter.\n *\n * @returns Deterministic relative files\n */\nexport function generatePolicyStarterFiles(): PolicyStarterFile[] {\n return POLICY_STARTER_FILES.map((file) => ({ ...file }));\n}\n\n// ---------------------------------------------------------------------------\n// Workspace-only init files (no bundles)\n// ---------------------------------------------------------------------------\n\n/** Files created by `policy init` — empty workspace only. */\nconst POLICY_INIT_WORKSPACE_FILES: readonly PolicyStarterFile[] = [\n {\n path: '.regal/config.yaml',\n contents: POLICY_REGAL_CONFIG_EMPTY_TEMPLATE,\n description: `Pin strict Rego v1 linting to OPA ${POLICY_STARTER_OPA_VERSION} capabilities`,\n },\n {\n path: 'README.md',\n contents: POLICY_README_TEMPLATE,\n description: 'Create the concise multi-bundle workspace guide',\n },\n];\n\n/**\n * Generate files for an empty workspace (init without bundles).\n *\n * @returns Deterministic workspace files\n */\nexport function generatePolicyWorkspaceFiles(): PolicyStarterFile[] {\n return POLICY_INIT_WORKSPACE_FILES.map((file) => ({ ...file }));\n}\n\n// ---------------------------------------------------------------------------\n// Policy bundle templates (used by `policy new`)\n// ---------------------------------------------------------------------------\n\n/** Interactive labels for each policy template. */\nexport const POLICY_TEMPLATE_PROMPT_LABELS: Record<PolicyTemplateName, string> = {\n [PolicyTemplate.Generic]: 'Generic example',\n [PolicyTemplate.Permissions]: 'Permission API starter',\n};\n\n/** Default root names for each template. */\nexport const POLICY_TEMPLATE_DEFAULT_ROOTS: Record<PolicyTemplateName, string> = {\n [PolicyTemplate.Generic]: 'example',\n [PolicyTemplate.Permissions]: PERMISSIONS_POLICY_BUNDLE_NAME,\n};\n\n/**\n * Build the bundle directory name from a root.\n *\n * @param root - Package root\n * @returns Bundle directory name\n */\nexport function buildBundleDirectoryName(root: string): string {\n return `${root}-bundle`;\n}\n\n/**\n * Validate a local bundle directory basename (under the policy workspace).\n *\n * @param value - Proposed directory basename\n * @returns True or error message\n */\nexport function validateBundleDirectoryName(value: string): true | string {\n if (!value) {\n return 'Enter a bundle directory name.';\n }\n if (value === '.' || value === '..' || value.includes('/') || value.includes('\\\\')) {\n return 'Bundle directory must be a single path segment (no slashes).';\n }\n if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(value)) {\n return 'Bundle directory must be alphanumeric (plus ., _, -), e.g. \"my-bundle\".';\n }\n if (value.length > 128) {\n return 'Bundle directory cannot exceed 128 characters.';\n }\n return true;\n}\n\n/**\n * Generate bundle files for the generic template.\n *\n * @param root - Package root name\n * @param bundleDir - Publish directory basename under the workspace\n * @returns Bundle files relative to the workspace\n */\nexport function generateGenericBundleFiles(\n root: string,\n bundleDir: string = buildBundleDirectoryName(root),\n): PolicyStarterFile[] {\n const bundle = bundleDir;\n return [\n {\n path: `${bundle}/${POLICY_MANIFEST_FILENAME}`,\n contents: buildPolicyManifestTemplate(root, PolicyTemplate.Generic),\n description: 'Create the OPA bundle .manifest',\n },\n {\n path: `${bundle}/${POLICY_INPUT_SCHEMA_FILENAME}`,\n contents: `{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": \"https://transcend.io/policy-schemas/${root}/input.json\",\n \"title\": \"${root === 'example' ? 'Example policy input' : `${root} policy input`}\",\n \"description\": \"${root === 'example' ? 'Disposable teaching envelope for the example bundle.' : `Input envelope for the ${root} bundle.`}\",\n \"type\": \"object\",\n \"additionalProperties\": true,\n \"properties\": {\n \"subject\": {\n \"type\": \"object\",\n \"additionalProperties\": true,\n \"properties\": {\n \"trusted\": {\n \"type\": \"boolean\",\n \"description\": \"When true, the ${root === 'example' ? 'example' : root} policy allows; otherwise it denies.\"\n }\n }\n }\n }\n}\n`,\n description: `Create the input JSON Schema for the ${root} bundle`,\n },\n {\n path: `${bundle}/${root}/result/result.rego`,\n contents: `# METADATA\n# title: ${root === 'example' ? 'Disposable example result' : `${root} result`}\n# description: |\n# A fail-closed ${root === 'example' ? 'teaching ' : ''}entrypoint. Replace this ${root === 'example' ? 'example ' : ''}with the document\n# tree and result contract required by your application.\n# scope: package\n# entrypoint: true\npackage ${root}.result\n\nimport rego.v1\n\ndefault decision := \"deny\"\n\ndecision := \"allow\" if {\n\\tinput.subject.trusted == true\n}\n\ndefault reason_code := \"untrusted_or_invalid_input\"\n\nreason_code := \"trusted_subject\" if {\n\\tdecision == \"allow\"\n}\n`,\n description: 'Create a documented fail-closed result entrypoint',\n },\n {\n path: `${bundle}/${root}/result/result_test.rego`,\n contents: `package ${root}.result_test\n\nimport data.${root}.result\nimport rego.v1\n\ntest_denies_untrusted_subject if {\n\\tresult.decision == \"deny\" with input as {\"subject\": {\"trusted\": false}}\n\\tresult.reason_code == \"untrusted_or_invalid_input\" with input as {\"subject\": {\"trusted\": false}}\n}\n\ntest_fails_closed_for_missing_subject if {\n\\tresult.decision == \"deny\" with input as {}\n\\tresult.reason_code == \"untrusted_or_invalid_input\" with input as {}\n}\n\ntest_allows_trusted_subject if {\n\\tresult.decision == \"allow\" with input as {\"subject\": {\"trusted\": true}}\n\\tresult.reason_code == \"trusted_subject\" with input as {\"subject\": {\"trusted\": true}}\n}\n`,\n description: 'Create executable tests for allow and fail-closed decisions',\n },\n {\n path: `${bundle}/input.example.json`,\n contents: POLICY_INPUT_EXAMPLE_TEMPLATE,\n description: 'Create a sanitized local evaluation input example',\n },\n {\n path: `${bundle}/input.json`,\n contents: POLICY_INPUT_EXAMPLE_TEMPLATE,\n description: 'Create a local evaluation input (gitignored copy of the example)',\n },\n {\n path: `${bundle}/.gitignore`,\n contents: POLICY_GITIGNORE_TEMPLATE,\n description: 'Ignore only the private local input file',\n },\n ];\n}\n\n/**\n * Generate bundle files for the permissions template.\n *\n * @param root - Package root name\n * @returns Bundle files relative to the workspace\n */\n/**\n * Serialize the published Permissions input schema for the local bundle path.\n *\n * Written as `{bundle}/input.schema.json` so editors and `opa --schema` can\n * type-check the envelope offline without a workspace `schemas/` directory.\n *\n * @returns Schema file contents ending in a trailing newline\n */\nexport function buildPermissionsInputSchemaContents(): string {\n return `${JSON.stringify(permissionsPolicyInputSchema, null, 2)}\\n`;\n}\n\n/**\n * Serialize the published Permissions input example for local evaluation.\n *\n * @returns Example input JSON ending in a trailing newline\n */\nexport function buildPermissionsInputExampleContents(): string {\n const [example] = permissionsPolicyInputSchema.examples;\n if (!example) {\n throw new Error('permissions-policy-input.json must declare at least one example');\n }\n return `${JSON.stringify(example, null, 2)}\\n`;\n}\n\n/**\n * Generate bundle files for the permissions template.\n *\n * @param root - Package root name\n * @param bundleDir - Publish directory basename under the workspace\n * @returns Bundle files relative to the workspace\n */\nexport function generatePermissionsBundleFiles(\n root: string,\n bundleDir: string = buildBundleDirectoryName(root),\n): PolicyStarterFile[] {\n const bundle = bundleDir;\n const inputExample = buildPermissionsInputExampleContents();\n return [\n {\n path: `${bundle}/${POLICY_MANIFEST_FILENAME}`,\n contents: buildPolicyManifestTemplate(root, PolicyTemplate.Permissions),\n description: 'Create the OPA bundle .manifest',\n },\n {\n path: `${bundle}/${POLICY_INPUT_SCHEMA_FILENAME}`,\n contents: buildPermissionsInputSchemaContents(),\n description: `Create the input JSON Schema for the ${root} bundle (from published permissions-policy-input.json)`,\n },\n {\n path: `${bundle}/${root}/config/config.rego`,\n contents: `# METADATA\n# title: Permissions config\n# description: |\n# Static bundle configuration. Values come from data.json in this directory.\npackage ${root}.config\n\nimport rego.v1\n`,\n description: 'Create the static bundle configuration package',\n },\n {\n path: `${bundle}/${root}/config/data.json`,\n contents: `{\n \"default_consent_allowed\": true,\n \"purposes\": [\n \"Analytics\",\n \"SaleOfInfo\"\n ],\n \"default_consent\": {}\n}\n`,\n description: 'Create the static purpose configuration data',\n },\n {\n path: `${bundle}/${root}/helpers/preference/preference.rego`,\n contents: `# METADATA\n# title: Purpose preference resolution\n# description: |\n# Resolves the subject's recorded choice for one purpose from the OPA\n# \\`input.preferences\\` array Sombra injects (one \\`{name, choice,\n# days_since_choice?}\\` entry per purpose; \\`choice\\` may be null when unset).\npackage ${root}.helpers.preference\n\nimport rego.v1\n\nimport data.${root}.config\n\nraw_preferences := object.get(input, \"preferences\", [])\n\nsource_available if {\n\\tis_array(raw_preferences)\n\\tevery entry in raw_preferences {\n\\t\\tis_object(entry)\n\\t\\tis_string(entry.name)\n\\t\\tentry.name != \"\"\n\\t}\n}\n\n# Boolean choice for a purpose, last-wins across duplicate entries (matching\n# Sombra's own dedup semantics). Returns the boolean only when a usable choice\n# exists; otherwise the rule is undefined and the purpose is undecided.\nchoice(name) := choice if {\n\\tsource_available\n\\tmatching := [entry | some entry in raw_preferences; entry.name == name]\n\\tcount(matching) > 0\n\\tlast := matching[count(matching) - 1]\n\\tchoice := object.get(last, \"choice\", null)\n\\tis_boolean(choice)\n}\n\nenabled(name) if choice(name) == true\n\ndisabled(name) if choice(name) == false\n\ndecided(name) if enabled(name)\n\ndecided(name) if disabled(name)\n\n# A configured purpose with no usable boolean choice is undecided: the subject\n# never chose, so the resolved default applies (not an opt-out).\nundecided(name) if {\n\\tname in config.purposes\n\\tnot enabled(name)\n\\tnot disabled(name)\n}\n`,\n description: 'Create purpose preference resolution helpers',\n },\n {\n path: `${bundle}/${root}/helpers/preference/preference_test.rego`,\n contents: `# Mechanical tests for purpose preference resolution.\n# Policy outcomes are tested beside the public entrypoint.\npackage ${root}.helpers.preference_test\n\nimport rego.v1\n\nimport data.${root}.helpers.preference\n\ngiven(preferences) := {\"preferences\": preferences}\n\ntest_treats_an_absent_preferences_array_as_no_recorded_choices if {\n\\tfixture := {}\n\n\\tpreference.source_available with input as fixture\n\\tpreference.undecided(\"Analytics\") with input as fixture\n}\n\ntest_accepts_an_empty_preferences_array if {\n\\tfixture := given([])\n\n\\tpreference.source_available with input as fixture\n\\tpreference.undecided(\"Analytics\") with input as fixture\n}\n\ntest_reports_a_non_array_preferences_as_unavailable if {\n\\tnot preference.source_available with input as {\"preferences\": {\"Analytics\": true}}\n}\n\ntest_reports_entries_without_a_usable_name_as_unavailable if {\n\\tnot preference.source_available with input as {\"preferences\": [{}]}\n\\tnot preference.source_available with input as {\"preferences\": [{\"name\": \"\"}]}\n\\tnot preference.source_available with input as {\"preferences\": [{\"choice\": true}]}\n}\n\ntest_distinguishes_enabled_and_disabled_choices if {\n\\tfixture := given([\n\\t\\t{\"name\": \"Analytics\", \"choice\": true},\n\\t\\t{\"name\": \"SaleOfInfo\", \"choice\": false},\n\\t])\n\n\\tpreference.enabled(\"Analytics\") with input as fixture\n\\tpreference.disabled(\"SaleOfInfo\") with input as fixture\n\\tpreference.decided(\"Analytics\") with input as fixture\n\\tpreference.decided(\"SaleOfInfo\") with input as fixture\n\\tnot preference.undecided(\"Analytics\") with input as fixture\n}\n\ntest_treats_a_null_choice_as_undecided if {\n\\tfixture := given([{\"name\": \"Analytics\", \"choice\": null}])\n\n\\tpreference.undecided(\"Analytics\") with input as fixture\n\\tnot preference.decided(\"Analytics\") with input as fixture\n}\n\ntest_treats_an_absent_choice_key_as_undecided if {\n\\tfixture := given([{\"name\": \"Analytics\", \"channel\": \"email\"}])\n\n\\tpreference.undecided(\"Analytics\") with input as fixture\n}\n\ntest_treats_a_non_boolean_choice_as_undecided if {\n\\tfixture := given([{\"name\": \"SaleOfInfo\", \"choice\": \"Daily\"}])\n\n\\tpreference.undecided(\"SaleOfInfo\") with input as fixture\n\\tnot preference.decided(\"SaleOfInfo\") with input as fixture\n}\n\n# Sombra dedupes by purpose before sending; last-wins keeps the bundle correct\n# against hand-crafted input that names the same purpose twice.\ntest_last_wins_on_duplicate_entries if {\n\\tfixture := given([\n\\t\\t{\"name\": \"SaleOfInfo\", \"choice\": true},\n\\t\\t{\"name\": \"SaleOfInfo\", \"choice\": false},\n\\t])\n\n\\tpreference.disabled(\"SaleOfInfo\") with input as fixture\n\\tnot preference.enabled(\"SaleOfInfo\") with input as fixture\n}\n\n# Extra fields on an entry (evidence, days_since_choice, channel) are ignored\n# rather than treated as a choice.\ntest_ignores_fields_beyond_the_recorded_choice if {\n\\tfixture := given([{\n\\t\\t\"name\": \"Analytics\",\n\\t\\t\"choice\": true,\n\\t\\t\"evidence\": \"consent_banner\",\n\\t\\t\"days_since_choice\": 45,\n\\t\\t\"channel\": \"email\",\n\\t}])\n\n\\tpreference.enabled(\"Analytics\") with input as fixture\n}\n`,\n description: 'Create preference resolution tests',\n },\n {\n path: `${bundle}/${root}/purposes/analytics/analytics.rego`,\n contents: `# METADATA\n# title: Analytics purpose decision\n# description: |\n# The Analytics purpose's verdict as a \\`result\\` document\n# (\\`data.${root}.purposes.analytics.result\\`).\npackage ${root}.purposes.analytics\n\nimport rego.v1\n\nimport data.${root}.helpers.preference\n\ndefault result := {\n\\t\"decision\": \"deny\",\n\\t\"reason_code\": \"default_deny\",\n}\n\nresult := {\n\\t\"decision\": \"allow\",\n\\t\"reason_code\": \"explicit_allow\",\n} if {\n\\tpreference.enabled(\"Analytics\")\n}\n\nresult := {\n\\t\"decision\": \"deny\",\n\\t\"reason_code\": \"explicit_deny\",\n} if {\n\\tpreference.disabled(\"Analytics\")\n}\n\nresult := {\n\\t\"decision\": \"allow\",\n\\t\"reason_code\": \"default_allow\",\n} if {\n\\tpreference.undecided(\"Analytics\")\n}\n`,\n description: 'Create the analytics purpose decision rule',\n },\n {\n path: `${bundle}/${root}/purposes/analytics/analytics_test.rego`,\n contents: `# Policy outcomes for the Analytics purpose (the queried document).\npackage ${root}.purposes.analytics_test\n\nimport rego.v1\n\nimport data.${root}.purposes.analytics\n\ngiven(preferences) := {\"preferences\": preferences}\n\ntest_explicit_allow if {\n\\tresult := analytics.result with input as given([{\"name\": \"Analytics\", \"choice\": true}])\n\\tresult == {\n\\t\\t\"decision\": \"allow\",\n\\t\\t\"reason_code\": \"explicit_allow\",\n\\t}\n}\n\ntest_explicit_deny if {\n\\tresult := analytics.result with input as given([{\"name\": \"Analytics\", \"choice\": false}])\n\\tresult == {\n\\t\\t\"decision\": \"deny\",\n\\t\\t\"reason_code\": \"explicit_deny\",\n\\t}\n}\n\ntest_undecided_resolves_to_default_allow if {\n\\tresult := analytics.result with input as given([])\n\\tresult == {\n\\t\\t\"decision\": \"allow\",\n\\t\\t\"reason_code\": \"default_allow\",\n\\t}\n}\n\ntest_missing_preferences_resolves_to_default_allow if {\n\\tresult := analytics.result with input as {}\n\\tresult == {\n\\t\\t\"decision\": \"allow\",\n\\t\\t\"reason_code\": \"default_allow\",\n\\t}\n}\n\ntest_non_boolean_choice_falls_back_to_default_allow if {\n\\tresult := analytics.result with input as given([{\"name\": \"Analytics\", \"choice\": \"yes\"}])\n\\tresult == {\n\\t\\t\"decision\": \"allow\",\n\\t\\t\"reason_code\": \"default_allow\",\n\\t}\n}\n\ntest_malformed_preferences_are_treated_as_undecided if {\n\\t# source_available is false, so enabled/disabled never fire; undecided still\n\\t# holds for configured purposes (not enabled and not disabled).\n\\tresult := analytics.result with input as {\"preferences\": {\"Analytics\": true}}\n\\tresult == {\n\\t\\t\"decision\": \"allow\",\n\\t\\t\"reason_code\": \"default_allow\",\n\\t}\n}\n`,\n description: 'Create analytics purpose decision tests',\n },\n {\n path: `${bundle}/${root}/purposes/entrypoint.rego`,\n contents: `# METADATA\n# title: Purpose decisions\n# description: Returns each purpose's decision and reason code.\n# scope: package\n# entrypoint: true\npackage ${root}.purposes\n\nimport rego.v1\n`,\n description: 'Create the purposes entrypoint',\n },\n {\n path: `${bundle}/input.example.json`,\n contents: inputExample,\n description: 'Create a sanitized permissions evaluation input example',\n },\n {\n path: `${bundle}/input.json`,\n contents: inputExample,\n description: 'Create a local evaluation input (gitignored copy of the example)',\n },\n {\n path: `${bundle}/.gitignore`,\n contents: POLICY_GITIGNORE_TEMPLATE,\n description: 'Ignore only the private local input file',\n },\n ];\n}\n\n/**\n * Generate bundle files for a given template and root.\n *\n * @param template - Template name\n * @param root - Package root name\n * @param bundleDir - Publish directory basename under the workspace\n * @returns Bundle files relative to the workspace\n */\nexport function generatePolicyBundleFiles(\n template: PolicyTemplateName,\n root: string,\n bundleDir: string = buildBundleDirectoryName(root),\n): PolicyStarterFile[] {\n switch (template) {\n case PolicyTemplate.Generic:\n return generateGenericBundleFiles(root, bundleDir);\n case PolicyTemplate.Permissions:\n return generatePermissionsBundleFiles(root, bundleDir);\n default:\n throw new Error(`Unknown policy template: ${String(template)}`);\n }\n}\n\n/**\n * Build the suggested `policy eval` follow-up after `policy new`.\n *\n * @param options - Bundle display path, package root, and template\n * @returns Multi-line shell command ready to paste\n */\nexport function buildPolicyBundleEvalNextStep(options: {\n /** Workspace-relative publish directory (e.g. `transcend/policy/permissions-bundle`). */\n bundleDirectory: string;\n /** Rego / `.manifest` package root. */\n root: string;\n /** Scaffold template that determined the query path. */\n template: PolicyTemplateName;\n}): string {\n const { bundleDirectory, root, template } = options;\n const packagePath =\n template === PolicyTemplate.Permissions ? `data.${root}.purposes` : `data.${root}.result`;\n return [\n `transcend policy eval ${bundleDirectory} \\\\`,\n ` --package=${packagePath} \\\\`,\n ` --input=${bundleDirectory}/input.json \\\\`,\n ` --schema=${bundleDirectory}/${POLICY_INPUT_SCHEMA_FILENAME}`,\n ].join('\\n');\n}\n","import { join, resolve } from 'node:path';\n\nimport type { LocalContext } from '../../context.js';\nimport {\n collectProjectRelativePaths,\n discoverProjectRepository,\n} from '../scaffolding/project-discovery.js';\nimport type { PolicyProjectState } from './policy-scaffold-model.js';\nimport {\n POLICY_MANIFEST_FILENAME,\n POLICY_STARTER_BUNDLE_DIRECTORY,\n} from './policy-scaffold-templates.js';\n\n/** Default local policy workspace directory (shared Regal config + bundles). */\nexport const DEFAULT_POLICY_PROJECT_DIRECTORY = 'transcend/policy';\n\n/**\n * Resolve a selected policy project relative to the invocation directory.\n *\n * @param invocationDirectory - CLI working directory\n * @param directory - User-selected project directory\n * @returns Absolute policy project directory\n */\nexport function resolvePolicyProjectDirectory(\n invocationDirectory: string,\n directory: string = DEFAULT_POLICY_PROJECT_DIRECTORY,\n): string {\n return resolve(invocationDirectory, directory);\n}\n\n/**\n * Absolute path to the disposable starter publish directory under a workspace.\n *\n * @param workspaceDirectory - Absolute policy workspace directory\n * @returns Absolute starter bundle directory\n */\nexport function getPolicyStarterBundleDirectory(workspaceDirectory: string): string {\n return join(workspaceDirectory, POLICY_STARTER_BUNDLE_DIRECTORY);\n}\n\n/**\n * Resolve publishable bundle directories under a path.\n *\n * - If `directory` itself contains a `.manifest`, it is treated as one bundle.\n * - Otherwise immediate child directories that contain a `.manifest` are\n * returned in sorted order (multi-bundle workspace). Folder names are not\n * required to end in `-bundle`; that suffix is only the `policy new`\n * scaffolding convention.\n *\n * @param context - Filesystem-bearing CLI context\n * @param directory - Absolute workspace or bundle directory\n * @returns Absolute bundle directories to lint / test\n */\nexport function discoverPolicyBundleDirectories(\n context: Pick<LocalContext, 'fs'>,\n directory: string,\n): string[] {\n if (!context.fs.existsSync(directory) || !context.fs.statSync(directory).isDirectory()) {\n return [];\n }\n const manifestAtRoot = join(directory, POLICY_MANIFEST_FILENAME);\n if (context.fs.existsSync(manifestAtRoot) && context.fs.statSync(manifestAtRoot).isFile()) {\n return [directory];\n }\n return context.fs\n .readdirSync(directory, { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => join(directory, entry.name))\n .filter((bundleDirectory) => {\n const manifestPath = join(bundleDirectory, POLICY_MANIFEST_FILENAME);\n return context.fs.existsSync(manifestPath) && context.fs.statSync(manifestPath).isFile();\n })\n .sort((left, right) => left.localeCompare(right));\n}\n\n/**\n * Collect repository state required by the pure policy planner.\n *\n * @param context - CLI context\n * @param directory - User-selected target directory\n * @returns Policy project discovery state\n */\nexport function discoverPolicyProject(\n context: LocalContext,\n directory: string = DEFAULT_POLICY_PROJECT_DIRECTORY,\n): PolicyProjectState {\n const invocationDirectory = context.process.cwd();\n const targetDirectory = resolvePolicyProjectDirectory(invocationDirectory, directory);\n if (\n context.fs.existsSync(targetDirectory) &&\n !context.fs.statSync(targetDirectory).isDirectory()\n ) {\n throw new Error(`Policy target is not a directory: ${targetDirectory}`);\n }\n const repository = discoverProjectRepository(context, {\n invocationDirectory,\n targetDirectory,\n standaloneProjectRoot: invocationDirectory,\n });\n return {\n invocationDirectory,\n targetDirectory,\n ...repository,\n relativePaths: collectProjectRelativePaths(context, targetDirectory),\n relativeFilePaths: collectProjectRelativePaths(context, targetDirectory, {\n includeDirectories: false,\n }),\n };\n}\n\n/**\n * Resolve the starter bundle manifest path for a discovered workspace.\n *\n * @param state - Policy project state\n * @returns Absolute manifest path under the starter `{root}-bundle/`\n */\nexport function getPolicyManifestPath(state: PolicyProjectState): string {\n return join(getPolicyStarterBundleDirectory(state.targetDirectory), POLICY_MANIFEST_FILENAME);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"policy-project-discovery-oc49b2gr.mjs","names":["permissionsPolicyInputSchema.$id","CLI_VERSION","permissionsPolicyInputSchema","permissionsPolicyInputSchema.examples"],"sources":["../schema/permissions-policy-input.json","../src/lib/policy/policy-scaffold-templates.ts","../src/lib/policy/policy-project-discovery.ts"],"sourcesContent":["","import yaml from 'js-yaml';\n\nimport permissionsPolicyInputSchema from '../../../schema/permissions-policy-input.json' with { type: 'json' };\nimport { version as CLI_VERSION } from '../../constants.js';\n\n/** Published Permissions API OPA input schema `$id` (raw GitHub URL). */\nexport const PERMISSIONS_POLICY_INPUT_SCHEMA_ID = permissionsPolicyInputSchema.$id;\n\n/** OPA version targeted by generated policy authoring configuration. */\nexport const POLICY_STARTER_OPA_VERSION = '1.18.2';\n\n/**\n * Package-path prefix for the disposable starter bundle.\n *\n * Matches OPA `.manifest` `roots` and Regal `project.roots` (not a filesystem\n * folder name by itself). The default publish directory is `{root}-bundle/`;\n * `policy new --bundle-dir` can override that basename.\n */\nexport const POLICY_STARTER_ROOT = 'example';\n\n/** Publish directory name for the disposable starter (`{root}-bundle`). */\nexport const POLICY_STARTER_BUNDLE_DIRECTORY = `${POLICY_STARTER_ROOT}-bundle`;\n\n/** Input JSON Schema filename written beside each publish directory's fixtures. */\nexport const POLICY_INPUT_SCHEMA_FILENAME = 'input.schema.json';\n\n/** One deterministic file in the safe policy starter. */\nexport interface PolicyStarterFile {\n /** POSIX-style path relative to the policy workspace directory. */\n path: string;\n /** Complete UTF-8 file contents. */\n contents: string;\n /** Human-readable reason shown in the plan. */\n description: string;\n}\n\n/** OPA bundle manifest filename used for local authoring and upload. */\nexport const POLICY_MANIFEST_FILENAME = '.manifest';\n\n/**\n * Remote bundle name reserved for Permissions API.\n *\n * Upload treats every bundle the same; Sombra's Permissions API always queries\n * this fixed name (never taken from the request body).\n */\nexport const PERMISSIONS_POLICY_BUNDLE_NAME = 'permissions';\n\n/** OPA `.manifest` `metadata` key for Transcend authoring hints. */\nexport const POLICY_MANIFEST_TRANSCEND_METADATA_KEY = 'transcend.io';\n\n/** Supported policy bundle templates (enum-style constants). */\nexport const PolicyTemplate = {\n Generic: 'generic',\n Permissions: 'permissions',\n} as const;\n\n/** Supported policy bundle template names. */\nexport const POLICY_TEMPLATE_NAMES = [PolicyTemplate.Generic, PolicyTemplate.Permissions] as const;\n\n/** A supported policy bundle template name. */\nexport type PolicyTemplateName = (typeof PolicyTemplate)[keyof typeof PolicyTemplate];\n\n/**\n * OPA `.manifest` `metadata.transcend.io` key for the CLI version that\n * generated the scaffold template.\n */\nexport const POLICY_MANIFEST_TEMPLATE_VERSION_KEY = 'templateVersion';\n\n/**\n * Build an OPA bundle manifest for a given root and scaffold template.\n *\n * `metadata.transcend.io.template` and `templateVersion` are authoring hints\n * only — Policy Engine upload does not branch on them. Permissions API still\n * keys off the remote bundle name {@link PERMISSIONS_POLICY_BUNDLE_NAME}.\n * `templateVersion` records the `@transcend-io/cli` package version used to\n * generate the scaffold.\n *\n * @param root - Package root\n * @param template - Scaffold template that produced this bundle\n * @returns Manifest JSON\n */\nexport function buildPolicyManifestTemplate(\n root: string,\n template: PolicyTemplateName = PolicyTemplate.Generic,\n): string {\n return `{\n \"$schema\": \"https://openpolicyagent.org/schemas/bundle/v1/manifest.schema.json\",\n \"revision\": \"\",\n \"roots\": [\"${root}\"],\n \"rego_version\": 1,\n \"metadata\": {\n \"${POLICY_MANIFEST_TRANSCEND_METADATA_KEY}\": {\n \"template\": \"${template}\",\n \"${POLICY_MANIFEST_TEMPLATE_VERSION_KEY}\": \"${CLI_VERSION}\"\n }\n }\n}\n`;\n}\n\n/** OPA bundle manifest for the disposable starter publish directory. */\nexport const POLICY_MANIFEST_TEMPLATE = buildPolicyManifestTemplate(\n POLICY_STARTER_ROOT,\n PolicyTemplate.Generic,\n);\n\n/**\n * Build workspace-level Regal configuration for given roots.\n *\n * `project.roots` are package-path prefixes (same meaning as OPA `.manifest`\n * roots), not filesystem folders.\n *\n * @param roots - Package-path roots\n * @returns YAML configuration\n */\nexport function buildPolicyRegalConfigTemplate(roots: readonly string[]): string {\n const rootsSection =\n roots.length === 0\n ? ' roots: []'\n : ` roots:\\n${roots.map((root) => ` - ${root}`).join('\\n')}`;\n return `# Workspace-level Regal config for both publishable bundles under this tree.\n# \\`project.roots\\` are package-path prefixes (same meaning as OPA \\`.manifest\\`\n# roots), not filesystem folders. Each bundle also has its own \\`.manifest\\`.\ncapabilities:\n from:\n engine: opa\n version: v${POLICY_STARTER_OPA_VERSION}\nproject:\n${rootsSection}\n rego-version: 1\n`;\n}\n\n/**\n * Merge one package-path root into an existing Regal config without wiping\n * unrelated keys (custom rules, ignores, etc.).\n *\n * Comments and key order may be reformatted by the YAML serializer. Invalid\n * `project.roots` shapes throw instead of silently dropping prior roots.\n *\n * @param existingContents - Current `.regal/config.yaml` contents\n * @param rootToAdd - Package-path root to include\n * @returns Updated file contents and the full roots list\n */\nexport function mergePolicyRegalConfigRoots(\n existingContents: string,\n rootToAdd: string,\n): { contents: string; roots: string[] } {\n const trimmed = existingContents.trim();\n if (trimmed.length === 0) {\n const roots = [rootToAdd];\n return { contents: buildPolicyRegalConfigTemplate(roots), roots };\n }\n\n let parsed: unknown;\n try {\n parsed = yaml.load(existingContents);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n throw new Error(`.regal/config.yaml is not valid YAML: ${detail}`);\n }\n\n if (parsed === null || parsed === undefined) {\n const roots = [rootToAdd];\n return { contents: buildPolicyRegalConfigTemplate(roots), roots };\n }\n if (typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error('.regal/config.yaml must be a YAML mapping at the top level.');\n }\n\n const config = { ...(parsed as Record<string, unknown>) };\n const existingProject = config.project;\n if (\n existingProject !== undefined &&\n existingProject !== null &&\n (typeof existingProject !== 'object' || Array.isArray(existingProject))\n ) {\n throw new Error('.regal/config.yaml project must be a YAML mapping when present.');\n }\n\n const project: Record<string, unknown> = {\n ...((existingProject as Record<string, unknown> | undefined) ?? {}),\n };\n const existingRoots = project.roots;\n let roots: string[];\n if (existingRoots === undefined) {\n roots = [rootToAdd];\n } else if (\n Array.isArray(existingRoots) &&\n existingRoots.every((root): root is string => typeof root === 'string')\n ) {\n roots = [...new Set([...existingRoots, rootToAdd])].sort((left, right) =>\n left.localeCompare(right),\n );\n } else {\n throw new Error(\n '.regal/config.yaml project.roots must be an array of strings. ' +\n 'Fix the Regal config before running `transcend policy new`.',\n );\n }\n\n project.roots = roots;\n if (project['rego-version'] === undefined) {\n project['rego-version'] = 1;\n }\n config.project = project;\n\n if (config.capabilities === undefined) {\n config.capabilities = {\n from: {\n engine: 'opa',\n version: `v${POLICY_STARTER_OPA_VERSION}`,\n },\n };\n }\n\n const dumped = yaml.dump(config, {\n lineWidth: -1,\n noRefs: true,\n sortKeys: false,\n });\n return {\n contents: dumped.endsWith('\\n') ? dumped : `${dumped}\\n`,\n roots,\n };\n}\n\n/**\n * Workspace-level Regal configuration.\n *\n * `project.roots` are package-path prefixes (same meaning as OPA `.manifest`\n * roots), not filesystem folders.\n */\nexport const POLICY_REGAL_CONFIG_TEMPLATE = buildPolicyRegalConfigTemplate([POLICY_STARTER_ROOT]);\n\n/** Regal config for an empty workspace with no bundles yet. */\nexport const POLICY_REGAL_CONFIG_EMPTY_TEMPLATE = buildPolicyRegalConfigTemplate([]);\n\n/** Disposable, fail-closed example policy. */\nexport const POLICY_RESULT_REGO_TEMPLATE = `# METADATA\n# title: Disposable example result\n# description: |\n# A fail-closed teaching entrypoint. Replace this example with the document\n# tree and result contract required by your application.\n# scope: package\n# entrypoint: true\npackage ${POLICY_STARTER_ROOT}.result\n\nimport rego.v1\n\ndefault decision := \"deny\"\n\ndecision := \"allow\" if {\n\tinput.subject.trusted == true\n}\n\ndefault reason_code := \"untrusted_or_invalid_input\"\n\nreason_code := \"trusted_subject\" if {\n\tdecision == \"allow\"\n}\n`;\n\n/** Executable tests adjacent to the disposable example policy. */\nexport const POLICY_RESULT_TEST_REGO_TEMPLATE = `package ${POLICY_STARTER_ROOT}.result_test\n\nimport data.${POLICY_STARTER_ROOT}.result\nimport rego.v1\n\ntest_denies_untrusted_subject if {\n\tresult.decision == \"deny\" with input as {\"subject\": {\"trusted\": false}}\n\tresult.reason_code == \"untrusted_or_invalid_input\" with input as {\"subject\": {\"trusted\": false}}\n}\n\ntest_fails_closed_for_missing_subject if {\n\tresult.decision == \"deny\" with input as {}\n\tresult.reason_code == \"untrusted_or_invalid_input\" with input as {}\n}\n\ntest_allows_trusted_subject if {\n\tresult.decision == \"allow\" with input as {\"subject\": {\"trusted\": true}}\n\tresult.reason_code == \"trusted_subject\" with input as {\"subject\": {\"trusted\": true}}\n}\n`;\n\n/** Sanitized local input example containing no credentials or personal data. */\nexport const POLICY_INPUT_EXAMPLE_TEMPLATE = `{\n \"subject\": {\n \"trusted\": false\n }\n}\n`;\n\n/** Input JSON Schema for the disposable starter (editor + \\`opa check -s\\`). */\nexport const POLICY_INPUT_SCHEMA_TEMPLATE = `{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": \"https://transcend.io/policy-schemas/${POLICY_STARTER_ROOT}/input.json\",\n \"title\": \"Example policy input\",\n \"description\": \"Disposable teaching envelope for the example bundle.\",\n \"type\": \"object\",\n \"additionalProperties\": true,\n \"properties\": {\n \"subject\": {\n \"type\": \"object\",\n \"additionalProperties\": true,\n \"properties\": {\n \"trusted\": {\n \"type\": \"boolean\",\n \"description\": \"When true, the example policy allows; otherwise it denies.\"\n }\n }\n }\n }\n}\n`;\n\n/** Target-local ignore rules for private evaluation input only. */\nexport const POLICY_GITIGNORE_TEMPLATE = `# Local policy evaluation input (VS Code / Regal look for input.json here).\n/input.json\n`;\n\n/** Concise workspace-local authoring guide (multi-bundle, empty workspace). */\nexport const POLICY_README_TEMPLATE = `# Transcend Policy\n\nMulti-bundle workspace for Transcend Policy Engine bundles\n(OPA / Rego v1). Requires **Transcend CLI ≥ 11** (\\`transcend policy --help\\`\nshould list \\`init\\`, \\`new\\`, and positional \\`[directory]\\` args).\n\nEach \\`*-bundle/\\` directory is a separate publishable unit (own \\`.manifest\\`,\ninput, and query path). Add a new bundle with:\n\n\\`\\`\\`sh\ntranscend policy new\n\\`\\`\\`\n\n## Quick start\n\n\\`\\`\\`sh\ntranscend policy init # create the workspace\ntranscend policy new --template generic # add a bundle from a template\n\ntranscend policy lint --noInteractive\ntranscend policy test\ntranscend policy eval transcend/policy/example-bundle \\\\\n --package=data.example.result \\\\\n --input=transcend/policy/example-bundle/input.json\n\\`\\`\\`\n\n\\`policy new\\` also writes a gitignored \\`input.json\\` (copy of \\`input.example.json\\`)\nfor local Evaluate. \\`policy lint\\` / \\`policy test\\` default to every child with a\n\\`.manifest\\` and use bundle-mode \\`opa test -b\\`, so both input files can coexist\nwithout a merge error.\n\n## Layout\n\n\\`\\`\\`\ntranscend/policy/\n example-bundle/ # publish directory (added by policy new)\n example/ # package root (matches .manifest roots)\n input.example.json\n input.json # local only (gitignored)\n input.schema.json # input JSON Schema (editor + opa --schema)\n .regal/config.yaml\n\\`\\`\\`\n\nReplace or delete example bundles when you have a real policy. Publish each\nbundle separately (\\`transcend policy publish --remote-bundle-name … <dir>\\`).\n\n## Permissions API\n\nScaffold the Permissions starter, then simulate the query Sombra runs:\n\n\\`\\`\\`sh\ntranscend policy new \\\\\n --template permissions \\\\\n --name ${PERMISSIONS_POLICY_BUNDLE_NAME} \\\\\n --bundle-dir ${PERMISSIONS_POLICY_BUNDLE_NAME}-bundle \\\\\n --yes\n\ntranscend policy eval transcend/policy/${PERMISSIONS_POLICY_BUNDLE_NAME}-bundle \\\\\n --package=data.${PERMISSIONS_POLICY_BUNDLE_NAME}.purposes \\\\\n --input=transcend/policy/${PERMISSIONS_POLICY_BUNDLE_NAME}-bundle/input.json \\\\\n --schema=transcend/policy/${PERMISSIONS_POLICY_BUNDLE_NAME}-bundle/input.schema.json\n\\`\\`\\`\n\nUpload does **not** distinguish bundle kinds. What makes a bundle “Permissions”\nis the remote name plus where Sombra queries it:\n\n- **Permissions API** always loads the fixed remote bundle name\n \\`${PERMISSIONS_POLICY_BUNDLE_NAME}\\`. Publish that starter with\n \\`--remote-bundle-name=${PERMISSIONS_POLICY_BUNDLE_NAME}\\`.\n- **Generic / decide** bundles use any other \\`--remote-bundle-name\\` and the decide\n path.\n\nScaffolded \\`.manifest\\` files may include \\`metadata.transcend.io.template\\`\n(\\`generic\\` or \\`permissions\\`) and \\`templateVersion\\` (the \\`@transcend-io/cli\\`\nversion that generated the scaffold) as authoring hints. Upload ignores them;\n\\`policy publish\\` may warn when the template hint and \\`--remote-bundle-name\\` disagree.\n`;\n\n/** Workspace-relative path to the disposable example entrypoint. */\nexport const POLICY_STARTER_RESULT_REGO_PATH = `${POLICY_STARTER_BUNDLE_DIRECTORY}/${POLICY_STARTER_ROOT}/result/result.rego`;\n\n/** Workspace-relative path to the disposable example tests. */\nexport const POLICY_STARTER_RESULT_TEST_REGO_PATH = `${POLICY_STARTER_BUNDLE_DIRECTORY}/${POLICY_STARTER_ROOT}/result/result_test.rego`;\n\n/** Complete safe starter in deterministic plan order. */\nconst POLICY_STARTER_FILES: readonly PolicyStarterFile[] = [\n {\n path: `${POLICY_STARTER_BUNDLE_DIRECTORY}/${POLICY_MANIFEST_FILENAME}`,\n contents: POLICY_MANIFEST_TEMPLATE,\n description: 'Create the OPA bundle .manifest',\n },\n {\n path: '.regal/config.yaml',\n contents: POLICY_REGAL_CONFIG_TEMPLATE,\n description: `Pin strict Rego v1 linting to OPA ${POLICY_STARTER_OPA_VERSION} capabilities`,\n },\n {\n path: `${POLICY_STARTER_BUNDLE_DIRECTORY}/${POLICY_INPUT_SCHEMA_FILENAME}`,\n contents: POLICY_INPUT_SCHEMA_TEMPLATE,\n description: 'Create the input JSON Schema for the example bundle',\n },\n {\n path: POLICY_STARTER_RESULT_REGO_PATH,\n contents: POLICY_RESULT_REGO_TEMPLATE,\n description: 'Create a documented fail-closed result entrypoint',\n },\n {\n path: POLICY_STARTER_RESULT_TEST_REGO_PATH,\n contents: POLICY_RESULT_TEST_REGO_TEMPLATE,\n description: 'Create executable tests for allow and fail-closed decisions',\n },\n {\n path: `${POLICY_STARTER_BUNDLE_DIRECTORY}/input.example.json`,\n contents: POLICY_INPUT_EXAMPLE_TEMPLATE,\n description: 'Create a sanitized local evaluation input',\n },\n {\n path: `${POLICY_STARTER_BUNDLE_DIRECTORY}/.gitignore`,\n contents: POLICY_GITIGNORE_TEMPLATE,\n description: 'Ignore only the private local input file',\n },\n {\n path: 'README.md',\n contents: POLICY_README_TEMPLATE,\n description: 'Create the concise policy project guide',\n },\n];\n\n/**\n * Generate an independent copy of the safe policy starter.\n *\n * @returns Deterministic relative files\n */\nexport function generatePolicyStarterFiles(): PolicyStarterFile[] {\n return POLICY_STARTER_FILES.map((file) => ({ ...file }));\n}\n\n// ---------------------------------------------------------------------------\n// Workspace-only init files (no bundles)\n// ---------------------------------------------------------------------------\n\n/** Files created by `policy init` — empty workspace only. */\nconst POLICY_INIT_WORKSPACE_FILES: readonly PolicyStarterFile[] = [\n {\n path: '.regal/config.yaml',\n contents: POLICY_REGAL_CONFIG_EMPTY_TEMPLATE,\n description: `Pin strict Rego v1 linting to OPA ${POLICY_STARTER_OPA_VERSION} capabilities`,\n },\n {\n path: 'README.md',\n contents: POLICY_README_TEMPLATE,\n description: 'Create the concise multi-bundle workspace guide',\n },\n];\n\n/**\n * Generate files for an empty workspace (init without bundles).\n *\n * @returns Deterministic workspace files\n */\nexport function generatePolicyWorkspaceFiles(): PolicyStarterFile[] {\n return POLICY_INIT_WORKSPACE_FILES.map((file) => ({ ...file }));\n}\n\n// ---------------------------------------------------------------------------\n// Policy bundle templates (used by `policy new`)\n// ---------------------------------------------------------------------------\n\n/** Interactive labels for each policy template. */\nexport const POLICY_TEMPLATE_PROMPT_LABELS: Record<PolicyTemplateName, string> = {\n [PolicyTemplate.Generic]: 'Generic example',\n [PolicyTemplate.Permissions]: 'Permission API starter',\n};\n\n/** Default root names for each template. */\nexport const POLICY_TEMPLATE_DEFAULT_ROOTS: Record<PolicyTemplateName, string> = {\n [PolicyTemplate.Generic]: 'example',\n [PolicyTemplate.Permissions]: PERMISSIONS_POLICY_BUNDLE_NAME,\n};\n\n/**\n * Build the bundle directory name from a root.\n *\n * @param root - Package root\n * @returns Bundle directory name\n */\nexport function buildBundleDirectoryName(root: string): string {\n return `${root}-bundle`;\n}\n\n/**\n * Validate a local bundle directory basename (under the policy workspace).\n *\n * @param value - Proposed directory basename\n * @returns True or error message\n */\nexport function validateBundleDirectoryName(value: string): true | string {\n if (!value) {\n return 'Enter a bundle directory name.';\n }\n if (value === '.' || value === '..' || value.includes('/') || value.includes('\\\\')) {\n return 'Bundle directory must be a single path segment (no slashes).';\n }\n if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(value)) {\n return 'Bundle directory must be alphanumeric (plus ., _, -), e.g. \"my-bundle\".';\n }\n if (value.length > 128) {\n return 'Bundle directory cannot exceed 128 characters.';\n }\n return true;\n}\n\n/**\n * Generate bundle files for the generic template.\n *\n * @param root - Package root name\n * @param bundleDir - Publish directory basename under the workspace\n * @returns Bundle files relative to the workspace\n */\nexport function generateGenericBundleFiles(\n root: string,\n bundleDir: string = buildBundleDirectoryName(root),\n): PolicyStarterFile[] {\n const bundle = bundleDir;\n return [\n {\n path: `${bundle}/${POLICY_MANIFEST_FILENAME}`,\n contents: buildPolicyManifestTemplate(root, PolicyTemplate.Generic),\n description: 'Create the OPA bundle .manifest',\n },\n {\n path: `${bundle}/${POLICY_INPUT_SCHEMA_FILENAME}`,\n contents: `{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": \"https://transcend.io/policy-schemas/${root}/input.json\",\n \"title\": \"${root === 'example' ? 'Example policy input' : `${root} policy input`}\",\n \"description\": \"${root === 'example' ? 'Disposable teaching envelope for the example bundle.' : `Input envelope for the ${root} bundle.`}\",\n \"type\": \"object\",\n \"additionalProperties\": true,\n \"properties\": {\n \"subject\": {\n \"type\": \"object\",\n \"additionalProperties\": true,\n \"properties\": {\n \"trusted\": {\n \"type\": \"boolean\",\n \"description\": \"When true, the ${root === 'example' ? 'example' : root} policy allows; otherwise it denies.\"\n }\n }\n }\n }\n}\n`,\n description: `Create the input JSON Schema for the ${root} bundle`,\n },\n {\n path: `${bundle}/${root}/result/result.rego`,\n contents: `# METADATA\n# title: ${root === 'example' ? 'Disposable example result' : `${root} result`}\n# description: |\n# A fail-closed ${root === 'example' ? 'teaching ' : ''}entrypoint. Replace this ${root === 'example' ? 'example ' : ''}with the document\n# tree and result contract required by your application.\n# scope: package\n# entrypoint: true\npackage ${root}.result\n\nimport rego.v1\n\ndefault decision := \"deny\"\n\ndecision := \"allow\" if {\n\\tinput.subject.trusted == true\n}\n\ndefault reason_code := \"untrusted_or_invalid_input\"\n\nreason_code := \"trusted_subject\" if {\n\\tdecision == \"allow\"\n}\n`,\n description: 'Create a documented fail-closed result entrypoint',\n },\n {\n path: `${bundle}/${root}/result/result_test.rego`,\n contents: `package ${root}.result_test\n\nimport data.${root}.result\nimport rego.v1\n\ntest_denies_untrusted_subject if {\n\\tresult.decision == \"deny\" with input as {\"subject\": {\"trusted\": false}}\n\\tresult.reason_code == \"untrusted_or_invalid_input\" with input as {\"subject\": {\"trusted\": false}}\n}\n\ntest_fails_closed_for_missing_subject if {\n\\tresult.decision == \"deny\" with input as {}\n\\tresult.reason_code == \"untrusted_or_invalid_input\" with input as {}\n}\n\ntest_allows_trusted_subject if {\n\\tresult.decision == \"allow\" with input as {\"subject\": {\"trusted\": true}}\n\\tresult.reason_code == \"trusted_subject\" with input as {\"subject\": {\"trusted\": true}}\n}\n`,\n description: 'Create executable tests for allow and fail-closed decisions',\n },\n {\n path: `${bundle}/input.example.json`,\n contents: POLICY_INPUT_EXAMPLE_TEMPLATE,\n description: 'Create a sanitized local evaluation input example',\n },\n {\n path: `${bundle}/input.json`,\n contents: POLICY_INPUT_EXAMPLE_TEMPLATE,\n description: 'Create a local evaluation input (gitignored copy of the example)',\n },\n {\n path: `${bundle}/.gitignore`,\n contents: POLICY_GITIGNORE_TEMPLATE,\n description: 'Ignore only the private local input file',\n },\n ];\n}\n\n/**\n * Generate bundle files for the permissions template.\n *\n * @param root - Package root name\n * @returns Bundle files relative to the workspace\n */\n/**\n * Serialize the published Permissions input schema for the local bundle path.\n *\n * Written as `{bundle}/input.schema.json` so editors and `opa --schema` can\n * type-check the envelope offline without a workspace `schemas/` directory.\n *\n * @returns Schema file contents ending in a trailing newline\n */\nexport function buildPermissionsInputSchemaContents(): string {\n return `${JSON.stringify(permissionsPolicyInputSchema, null, 2)}\\n`;\n}\n\n/**\n * Serialize the published Permissions input example for local evaluation.\n *\n * @returns Example input JSON ending in a trailing newline\n */\nexport function buildPermissionsInputExampleContents(): string {\n const [example] = permissionsPolicyInputSchema.examples;\n if (!example) {\n throw new Error('permissions-policy-input.json must declare at least one example');\n }\n return `${JSON.stringify(example, null, 2)}\\n`;\n}\n\n/**\n * Generate bundle files for the permissions template.\n *\n * @param root - Package root name\n * @param bundleDir - Publish directory basename under the workspace\n * @returns Bundle files relative to the workspace\n */\nexport function generatePermissionsBundleFiles(\n root: string,\n bundleDir: string = buildBundleDirectoryName(root),\n): PolicyStarterFile[] {\n const bundle = bundleDir;\n const inputExample = buildPermissionsInputExampleContents();\n return [\n {\n path: `${bundle}/${POLICY_MANIFEST_FILENAME}`,\n contents: buildPolicyManifestTemplate(root, PolicyTemplate.Permissions),\n description: 'Create the OPA bundle .manifest',\n },\n {\n path: `${bundle}/${POLICY_INPUT_SCHEMA_FILENAME}`,\n contents: buildPermissionsInputSchemaContents(),\n description: `Create the input JSON Schema for the ${root} bundle (from published permissions-policy-input.json)`,\n },\n {\n path: `${bundle}/${root}/config/config.rego`,\n contents: `# METADATA\n# title: Permissions config\n# description: |\n# Static bundle configuration. Values come from data.json in this directory.\npackage ${root}.config\n\nimport rego.v1\n`,\n description: 'Create the static bundle configuration package',\n },\n {\n path: `${bundle}/${root}/config/data.json`,\n contents: `{\n \"default_consent_allowed\": true,\n \"purposes\": [\n \"Analytics\",\n \"SaleOfInfo\"\n ],\n \"default_consent\": {}\n}\n`,\n description: 'Create the static purpose configuration data',\n },\n {\n path: `${bundle}/${root}/helpers/preference/preference.rego`,\n contents: `# METADATA\n# title: Purpose preference resolution\n# description: |\n# Resolves the subject's recorded choice for one purpose from the OPA\n# \\`input.preferences\\` array Sombra injects (one \\`{name, choice,\n# days_since_choice?}\\` entry per purpose; \\`choice\\` may be null when unset).\npackage ${root}.helpers.preference\n\nimport rego.v1\n\nimport data.${root}.config\n\nraw_preferences := object.get(input, \"preferences\", [])\n\nsource_available if {\n\\tis_array(raw_preferences)\n\\tevery entry in raw_preferences {\n\\t\\tis_object(entry)\n\\t\\tis_string(entry.name)\n\\t\\tentry.name != \"\"\n\\t}\n}\n\n# Boolean choice for a purpose, last-wins across duplicate entries (matching\n# Sombra's own dedup semantics). Returns the boolean only when a usable choice\n# exists; otherwise the rule is undefined and the purpose is undecided.\nchoice(name) := choice if {\n\\tsource_available\n\\tmatching := [entry | some entry in raw_preferences; entry.name == name]\n\\tcount(matching) > 0\n\\tlast := matching[count(matching) - 1]\n\\tchoice := object.get(last, \"choice\", null)\n\\tis_boolean(choice)\n}\n\nenabled(name) if choice(name) == true\n\ndisabled(name) if choice(name) == false\n\ndecided(name) if enabled(name)\n\ndecided(name) if disabled(name)\n\n# A configured purpose with no usable boolean choice is undecided: the subject\n# never chose, so the resolved default applies (not an opt-out).\nundecided(name) if {\n\\tname in config.purposes\n\\tnot enabled(name)\n\\tnot disabled(name)\n}\n`,\n description: 'Create purpose preference resolution helpers',\n },\n {\n path: `${bundle}/${root}/helpers/preference/preference_test.rego`,\n contents: `# Mechanical tests for purpose preference resolution.\n# Policy outcomes are tested beside the public entrypoint.\npackage ${root}.helpers.preference_test\n\nimport rego.v1\n\nimport data.${root}.helpers.preference\n\ngiven(preferences) := {\"preferences\": preferences}\n\ntest_treats_an_absent_preferences_array_as_no_recorded_choices if {\n\\tfixture := {}\n\n\\tpreference.source_available with input as fixture\n\\tpreference.undecided(\"Analytics\") with input as fixture\n}\n\ntest_accepts_an_empty_preferences_array if {\n\\tfixture := given([])\n\n\\tpreference.source_available with input as fixture\n\\tpreference.undecided(\"Analytics\") with input as fixture\n}\n\ntest_reports_a_non_array_preferences_as_unavailable if {\n\\tnot preference.source_available with input as {\"preferences\": {\"Analytics\": true}}\n}\n\ntest_reports_entries_without_a_usable_name_as_unavailable if {\n\\tnot preference.source_available with input as {\"preferences\": [{}]}\n\\tnot preference.source_available with input as {\"preferences\": [{\"name\": \"\"}]}\n\\tnot preference.source_available with input as {\"preferences\": [{\"choice\": true}]}\n}\n\ntest_distinguishes_enabled_and_disabled_choices if {\n\\tfixture := given([\n\\t\\t{\"name\": \"Analytics\", \"choice\": true},\n\\t\\t{\"name\": \"SaleOfInfo\", \"choice\": false},\n\\t])\n\n\\tpreference.enabled(\"Analytics\") with input as fixture\n\\tpreference.disabled(\"SaleOfInfo\") with input as fixture\n\\tpreference.decided(\"Analytics\") with input as fixture\n\\tpreference.decided(\"SaleOfInfo\") with input as fixture\n\\tnot preference.undecided(\"Analytics\") with input as fixture\n}\n\ntest_treats_a_null_choice_as_undecided if {\n\\tfixture := given([{\"name\": \"Analytics\", \"choice\": null}])\n\n\\tpreference.undecided(\"Analytics\") with input as fixture\n\\tnot preference.decided(\"Analytics\") with input as fixture\n}\n\ntest_treats_an_absent_choice_key_as_undecided if {\n\\tfixture := given([{\"name\": \"Analytics\", \"channel\": \"email\"}])\n\n\\tpreference.undecided(\"Analytics\") with input as fixture\n}\n\ntest_treats_a_non_boolean_choice_as_undecided if {\n\\tfixture := given([{\"name\": \"SaleOfInfo\", \"choice\": \"Daily\"}])\n\n\\tpreference.undecided(\"SaleOfInfo\") with input as fixture\n\\tnot preference.decided(\"SaleOfInfo\") with input as fixture\n}\n\n# Sombra dedupes by purpose before sending; last-wins keeps the bundle correct\n# against hand-crafted input that names the same purpose twice.\ntest_last_wins_on_duplicate_entries if {\n\\tfixture := given([\n\\t\\t{\"name\": \"SaleOfInfo\", \"choice\": true},\n\\t\\t{\"name\": \"SaleOfInfo\", \"choice\": false},\n\\t])\n\n\\tpreference.disabled(\"SaleOfInfo\") with input as fixture\n\\tnot preference.enabled(\"SaleOfInfo\") with input as fixture\n}\n\n# Extra fields on an entry (evidence, days_since_choice, channel) are ignored\n# rather than treated as a choice.\ntest_ignores_fields_beyond_the_recorded_choice if {\n\\tfixture := given([{\n\\t\\t\"name\": \"Analytics\",\n\\t\\t\"choice\": true,\n\\t\\t\"evidence\": \"consent_banner\",\n\\t\\t\"days_since_choice\": 45,\n\\t\\t\"channel\": \"email\",\n\\t}])\n\n\\tpreference.enabled(\"Analytics\") with input as fixture\n}\n`,\n description: 'Create preference resolution tests',\n },\n {\n path: `${bundle}/${root}/purposes/analytics/analytics.rego`,\n contents: `# METADATA\n# title: Analytics purpose decision\n# description: |\n# The Analytics purpose's verdict as a \\`result\\` document\n# (\\`data.${root}.purposes.analytics.result\\`).\npackage ${root}.purposes.analytics\n\nimport rego.v1\n\nimport data.${root}.helpers.preference\n\ndefault result := {\n\\t\"decision\": \"deny\",\n\\t\"reason_code\": \"default_deny\",\n}\n\nresult := {\n\\t\"decision\": \"allow\",\n\\t\"reason_code\": \"explicit_allow\",\n} if {\n\\tpreference.enabled(\"Analytics\")\n}\n\nresult := {\n\\t\"decision\": \"deny\",\n\\t\"reason_code\": \"explicit_deny\",\n} if {\n\\tpreference.disabled(\"Analytics\")\n}\n\nresult := {\n\\t\"decision\": \"allow\",\n\\t\"reason_code\": \"default_allow\",\n} if {\n\\tpreference.undecided(\"Analytics\")\n}\n`,\n description: 'Create the analytics purpose decision rule',\n },\n {\n path: `${bundle}/${root}/purposes/analytics/analytics_test.rego`,\n contents: `# Policy outcomes for the Analytics purpose (the queried document).\npackage ${root}.purposes.analytics_test\n\nimport rego.v1\n\nimport data.${root}.purposes.analytics\n\ngiven(preferences) := {\"preferences\": preferences}\n\ntest_explicit_allow if {\n\\tresult := analytics.result with input as given([{\"name\": \"Analytics\", \"choice\": true}])\n\\tresult == {\n\\t\\t\"decision\": \"allow\",\n\\t\\t\"reason_code\": \"explicit_allow\",\n\\t}\n}\n\ntest_explicit_deny if {\n\\tresult := analytics.result with input as given([{\"name\": \"Analytics\", \"choice\": false}])\n\\tresult == {\n\\t\\t\"decision\": \"deny\",\n\\t\\t\"reason_code\": \"explicit_deny\",\n\\t}\n}\n\ntest_undecided_resolves_to_default_allow if {\n\\tresult := analytics.result with input as given([])\n\\tresult == {\n\\t\\t\"decision\": \"allow\",\n\\t\\t\"reason_code\": \"default_allow\",\n\\t}\n}\n\ntest_missing_preferences_resolves_to_default_allow if {\n\\tresult := analytics.result with input as {}\n\\tresult == {\n\\t\\t\"decision\": \"allow\",\n\\t\\t\"reason_code\": \"default_allow\",\n\\t}\n}\n\ntest_non_boolean_choice_falls_back_to_default_allow if {\n\\tresult := analytics.result with input as given([{\"name\": \"Analytics\", \"choice\": \"yes\"}])\n\\tresult == {\n\\t\\t\"decision\": \"allow\",\n\\t\\t\"reason_code\": \"default_allow\",\n\\t}\n}\n\ntest_malformed_preferences_are_treated_as_undecided if {\n\\t# source_available is false, so enabled/disabled never fire; undecided still\n\\t# holds for configured purposes (not enabled and not disabled).\n\\tresult := analytics.result with input as {\"preferences\": {\"Analytics\": true}}\n\\tresult == {\n\\t\\t\"decision\": \"allow\",\n\\t\\t\"reason_code\": \"default_allow\",\n\\t}\n}\n`,\n description: 'Create analytics purpose decision tests',\n },\n {\n path: `${bundle}/${root}/purposes/entrypoint.rego`,\n contents: `# METADATA\n# title: Purpose decisions\n# description: Returns each purpose's decision and reason code.\n# scope: package\n# entrypoint: true\npackage ${root}.purposes\n\nimport rego.v1\n`,\n description: 'Create the purposes entrypoint',\n },\n {\n path: `${bundle}/input.example.json`,\n contents: inputExample,\n description: 'Create a sanitized permissions evaluation input example',\n },\n {\n path: `${bundle}/input.json`,\n contents: inputExample,\n description: 'Create a local evaluation input (gitignored copy of the example)',\n },\n {\n path: `${bundle}/.gitignore`,\n contents: POLICY_GITIGNORE_TEMPLATE,\n description: 'Ignore only the private local input file',\n },\n ];\n}\n\n/**\n * Generate bundle files for a given template and root.\n *\n * @param template - Template name\n * @param root - Package root name\n * @param bundleDir - Publish directory basename under the workspace\n * @returns Bundle files relative to the workspace\n */\nexport function generatePolicyBundleFiles(\n template: PolicyTemplateName,\n root: string,\n bundleDir: string = buildBundleDirectoryName(root),\n): PolicyStarterFile[] {\n switch (template) {\n case PolicyTemplate.Generic:\n return generateGenericBundleFiles(root, bundleDir);\n case PolicyTemplate.Permissions:\n return generatePermissionsBundleFiles(root, bundleDir);\n default:\n throw new Error(`Unknown policy template: ${String(template)}`);\n }\n}\n\n/**\n * Build the suggested `policy eval` follow-up after `policy new`.\n *\n * @param options - Bundle display path, package root, and template\n * @returns Multi-line shell command ready to paste\n */\nexport function buildPolicyBundleEvalNextStep(options: {\n /** Workspace-relative publish directory (e.g. `transcend/policy/permissions-bundle`). */\n bundleDirectory: string;\n /** Rego / `.manifest` package root. */\n root: string;\n /** Scaffold template that determined the query path. */\n template: PolicyTemplateName;\n}): string {\n const { bundleDirectory, root, template } = options;\n const packagePath =\n template === PolicyTemplate.Permissions ? `data.${root}.purposes` : `data.${root}.result`;\n return [\n `transcend policy eval ${bundleDirectory} \\\\`,\n ` --package=${packagePath} \\\\`,\n ` --input=${bundleDirectory}/input.json \\\\`,\n ` --schema=${bundleDirectory}/${POLICY_INPUT_SCHEMA_FILENAME}`,\n ].join('\\n');\n}\n","import { join, resolve } from 'node:path';\n\nimport type { LocalContext } from '../../context.js';\nimport {\n collectProjectRelativePaths,\n discoverProjectRepository,\n} from '../scaffolding/project-discovery.js';\nimport type { PolicyProjectState } from './policy-scaffold-model.js';\nimport {\n POLICY_MANIFEST_FILENAME,\n POLICY_STARTER_BUNDLE_DIRECTORY,\n} from './policy-scaffold-templates.js';\n\n/** Default local policy workspace directory (shared Regal config + bundles). */\nexport const DEFAULT_POLICY_PROJECT_DIRECTORY = 'transcend/policy';\n\n/**\n * Resolve a selected policy project relative to the invocation directory.\n *\n * @param invocationDirectory - CLI working directory\n * @param directory - User-selected project directory\n * @returns Absolute policy project directory\n */\nexport function resolvePolicyProjectDirectory(\n invocationDirectory: string,\n directory: string = DEFAULT_POLICY_PROJECT_DIRECTORY,\n): string {\n return resolve(invocationDirectory, directory);\n}\n\n/**\n * Absolute path to the disposable starter publish directory under a workspace.\n *\n * @param workspaceDirectory - Absolute policy workspace directory\n * @returns Absolute starter bundle directory\n */\nexport function getPolicyStarterBundleDirectory(workspaceDirectory: string): string {\n return join(workspaceDirectory, POLICY_STARTER_BUNDLE_DIRECTORY);\n}\n\n/**\n * Resolve publishable bundle directories under a path.\n *\n * - If `directory` itself contains a `.manifest`, it is treated as one bundle.\n * - Otherwise immediate child directories that contain a `.manifest` are\n * returned in sorted order (multi-bundle workspace). Folder names are not\n * required to end in `-bundle`; that suffix is only the `policy new`\n * scaffolding convention.\n *\n * @param context - Filesystem-bearing CLI context\n * @param directory - Absolute workspace or bundle directory\n * @returns Absolute bundle directories to lint / test\n */\nexport function discoverPolicyBundleDirectories(\n context: Pick<LocalContext, 'fs'>,\n directory: string,\n): string[] {\n if (!context.fs.existsSync(directory) || !context.fs.statSync(directory).isDirectory()) {\n return [];\n }\n const manifestAtRoot = join(directory, POLICY_MANIFEST_FILENAME);\n if (context.fs.existsSync(manifestAtRoot) && context.fs.statSync(manifestAtRoot).isFile()) {\n return [directory];\n }\n return context.fs\n .readdirSync(directory, { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => join(directory, entry.name))\n .filter((bundleDirectory) => {\n const manifestPath = join(bundleDirectory, POLICY_MANIFEST_FILENAME);\n return context.fs.existsSync(manifestPath) && context.fs.statSync(manifestPath).isFile();\n })\n .sort((left, right) => left.localeCompare(right));\n}\n\n/**\n * Collect repository state required by the pure policy planner.\n *\n * @param context - CLI context\n * @param directory - User-selected target directory\n * @returns Policy project discovery state\n */\nexport function discoverPolicyProject(\n context: LocalContext,\n directory: string = DEFAULT_POLICY_PROJECT_DIRECTORY,\n): PolicyProjectState {\n const invocationDirectory = context.process.cwd();\n const targetDirectory = resolvePolicyProjectDirectory(invocationDirectory, directory);\n if (\n context.fs.existsSync(targetDirectory) &&\n !context.fs.statSync(targetDirectory).isDirectory()\n ) {\n throw new Error(`Policy target is not a directory: ${targetDirectory}`);\n }\n const repository = discoverProjectRepository(context, {\n invocationDirectory,\n targetDirectory,\n standaloneProjectRoot: invocationDirectory,\n });\n return {\n invocationDirectory,\n targetDirectory,\n ...repository,\n relativePaths: collectProjectRelativePaths(context, targetDirectory),\n relativeFilePaths: collectProjectRelativePaths(context, targetDirectory, {\n includeDirectories: false,\n }),\n };\n}\n\n/**\n * Resolve the starter bundle manifest path for a discovered workspace.\n *\n * @param state - Policy project state\n * @returns Absolute manifest path under the starter `{root}-bundle/`\n */\nexport function getPolicyManifestPath(state: PolicyProjectState): string {\n return join(getPolicyStarterBundleDirectory(state.targetDirectory), POLICY_MANIFEST_FILENAME);\n}\n"],"mappings":"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"}
|