@transcend-io/cli 12.1.0 → 12.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/README.md +2 -0
  2. package/dist/{app-CfJlhIn8.mjs → app-BAx2hvcN.mjs} +19 -17
  3. package/dist/{app-CfJlhIn8.mjs.map → app-BAx2hvcN.mjs.map} +1 -1
  4. package/dist/{approvePrivacyRequests-BWj6SsgX.mjs → approvePrivacyRequests-B-4o7QMG.mjs} +2 -2
  5. package/dist/{approvePrivacyRequests-BWj6SsgX.mjs.map → approvePrivacyRequests-B-4o7QMG.mjs.map} +1 -1
  6. package/dist/bin/bash-complete.mjs +1 -1
  7. package/dist/bin/cli.mjs +1 -1
  8. package/dist/bin/deprecated-command.mjs +1 -1
  9. package/dist/{buildExamples-1I3xKR1X.mjs → buildExamples-kR5_7dkU.mjs} +2 -2
  10. package/dist/{buildExamples-1I3xKR1X.mjs.map → buildExamples-kR5_7dkU.mjs.map} +1 -1
  11. package/dist/{buildXdiSyncEndpoint-CAal3Xc4.mjs → buildXdiSyncEndpoint-BSYwJ2vc.mjs} +2 -2
  12. package/dist/{buildXdiSyncEndpoint-CAal3Xc4.mjs.map → buildXdiSyncEndpoint-BSYwJ2vc.mjs.map} +1 -1
  13. package/dist/{bulkRestartRequests-B3wupylo.mjs → bulkRestartRequests-DhOfMz0S.mjs} +2 -2
  14. package/dist/{bulkRestartRequests-B3wupylo.mjs.map → bulkRestartRequests-DhOfMz0S.mjs.map} +1 -1
  15. package/dist/{bulkRetryEnrichers-D801tBcB.mjs → bulkRetryEnrichers-Cj94ftnY.mjs} +2 -2
  16. package/dist/{bulkRetryEnrichers-D801tBcB.mjs.map → bulkRetryEnrichers-Cj94ftnY.mjs.map} +1 -1
  17. package/dist/{cancelPrivacyRequests-CAKTZF47.mjs → cancelPrivacyRequests-DgAhfcmg.mjs} +2 -2
  18. package/dist/{cancelPrivacyRequests-CAKTZF47.mjs.map → cancelPrivacyRequests-DgAhfcmg.mjs.map} +1 -1
  19. package/dist/{command-BDQOL_MQ.mjs → command-gwG8sQwM.mjs} +2 -2
  20. package/dist/{command-BDQOL_MQ.mjs.map → command-gwG8sQwM.mjs.map} +1 -1
  21. package/dist/{constants-BlKpAm3R.mjs → constants-BCmVqlrM.mjs} +2 -2
  22. package/dist/{constants-BlKpAm3R.mjs.map → constants-BCmVqlrM.mjs.map} +1 -1
  23. package/dist/constants-DlH2vWm_.mjs +2 -0
  24. package/dist/constants-DlH2vWm_.mjs.map +1 -0
  25. package/dist/{createPoolingCommandUi-JBOW5UxY.mjs → createPoolingCommandUi-ByyTeJx5.mjs} +2 -2
  26. package/dist/{createPoolingCommandUi-JBOW5UxY.mjs.map → createPoolingCommandUi-ByyTeJx5.mjs.map} +1 -1
  27. package/dist/{downloadPrivacyRequestFiles-Cf14MQR4.mjs → downloadPrivacyRequestFiles-yiE6NzXz.mjs} +2 -2
  28. package/dist/{downloadPrivacyRequestFiles-Cf14MQR4.mjs.map → downloadPrivacyRequestFiles-yiE6NzXz.mjs.map} +1 -1
  29. package/dist/{extractClientError-DUJxHv07.mjs → extractClientError-BQgmnp3e.mjs} +2 -2
  30. package/dist/{extractClientError-DUJxHv07.mjs.map → extractClientError-BQgmnp3e.mjs.map} +1 -1
  31. package/dist/{fuzzyMatchColumns-2NmQqCZI.mjs → fuzzyMatchColumns-D18n9Beu.mjs} +2 -2
  32. package/dist/{fuzzyMatchColumns-2NmQqCZI.mjs.map → fuzzyMatchColumns-D18n9Beu.mjs.map} +1 -1
  33. package/dist/{generateCrossAccountApiKeys-CVdsJFOe.mjs → generateCrossAccountApiKeys-CoSsZwSz.mjs} +2 -2
  34. package/dist/{generateCrossAccountApiKeys-CVdsJFOe.mjs.map → generateCrossAccountApiKeys-CoSsZwSz.mjs.map} +1 -1
  35. package/dist/{impl-hRK54juc.mjs → impl-4WoLxjdU.mjs} +2 -2
  36. package/dist/{impl-hRK54juc.mjs.map → impl-4WoLxjdU.mjs.map} +1 -1
  37. package/dist/{impl-QdSegO65.mjs → impl-BCP4X_zk.mjs} +2 -2
  38. package/dist/{impl-QdSegO65.mjs.map → impl-BCP4X_zk.mjs.map} +1 -1
  39. package/dist/{impl-DTvnDByz.mjs → impl-BFSZEQ2s.mjs} +2 -2
  40. package/dist/{impl-DTvnDByz.mjs.map → impl-BFSZEQ2s.mjs.map} +1 -1
  41. package/dist/{impl-BgkovGOz.mjs → impl-BJnxahnW.mjs} +2 -2
  42. package/dist/{impl-BgkovGOz.mjs.map → impl-BJnxahnW.mjs.map} +1 -1
  43. package/dist/{impl-BxvKe9_7.mjs → impl-BKKBmXv4.mjs} +2 -2
  44. package/dist/{impl-BxvKe9_7.mjs.map → impl-BKKBmXv4.mjs.map} +1 -1
  45. package/dist/{impl-Tr08dgdk.mjs → impl-BM2SWSKF.mjs} +2 -2
  46. package/dist/{impl-Tr08dgdk.mjs.map → impl-BM2SWSKF.mjs.map} +1 -1
  47. package/dist/{impl-BGtup1F9.mjs → impl-BMX6B40F.mjs} +2 -2
  48. package/dist/{impl-BGtup1F9.mjs.map → impl-BMX6B40F.mjs.map} +1 -1
  49. package/dist/{impl-BgD7TeMD.mjs → impl-BRCAL4RM.mjs} +2 -2
  50. package/dist/{impl-BgD7TeMD.mjs.map → impl-BRCAL4RM.mjs.map} +1 -1
  51. package/dist/{impl-CG3f4B6u.mjs → impl-BaGN4J-w.mjs} +2 -2
  52. package/dist/{impl-CG3f4B6u.mjs.map → impl-BaGN4J-w.mjs.map} +1 -1
  53. package/dist/{impl-B-Oe7DcE2.mjs → impl-Bf2Qli6c2.mjs} +2 -2
  54. package/dist/{impl-B-Oe7DcE2.mjs.map → impl-Bf2Qli6c2.mjs.map} +1 -1
  55. package/dist/{impl-mjDxMEDR.mjs → impl-BrdCuU0I.mjs} +2 -2
  56. package/dist/{impl-mjDxMEDR.mjs.map → impl-BrdCuU0I.mjs.map} +1 -1
  57. package/dist/{impl-C-PvYlFE.mjs → impl-C0R2azuF.mjs} +2 -2
  58. package/dist/{impl-C-PvYlFE.mjs.map → impl-C0R2azuF.mjs.map} +1 -1
  59. package/dist/{impl-CC-yFYWt.mjs → impl-C1hh_5On.mjs} +2 -2
  60. package/dist/{impl-CC-yFYWt.mjs.map → impl-C1hh_5On.mjs.map} +1 -1
  61. package/dist/{impl-Bh02zrTq.mjs → impl-C1vn0Y7f.mjs} +2 -2
  62. package/dist/{impl-Bh02zrTq.mjs.map → impl-C1vn0Y7f.mjs.map} +1 -1
  63. package/dist/{impl-D6Lzwd-R2.mjs → impl-C54oUOeM2.mjs} +2 -2
  64. package/dist/{impl-D6Lzwd-R2.mjs.map → impl-C54oUOeM2.mjs.map} +1 -1
  65. package/dist/{impl-BopWGXnH.mjs → impl-CKg5Xu-r.mjs} +2 -2
  66. package/dist/{impl-BopWGXnH.mjs.map → impl-CKg5Xu-r.mjs.map} +1 -1
  67. package/dist/{impl-BQj1Ien4.mjs → impl-ClhMRgQZ.mjs} +2 -2
  68. package/dist/{impl-BQj1Ien4.mjs.map → impl-ClhMRgQZ.mjs.map} +1 -1
  69. package/dist/{impl-BQRP9NIv.mjs → impl-CuDZY6y7.mjs} +2 -2
  70. package/dist/{impl-BQRP9NIv.mjs.map → impl-CuDZY6y7.mjs.map} +1 -1
  71. package/dist/{impl-Cq-GARM1.mjs → impl-CxpGBeJ0.mjs} +2 -2
  72. package/dist/{impl-Cq-GARM1.mjs.map → impl-CxpGBeJ0.mjs.map} +1 -1
  73. package/dist/{impl-BQe6vddz.mjs → impl-CyDdvH5a.mjs} +2 -2
  74. package/dist/{impl-BQe6vddz.mjs.map → impl-CyDdvH5a.mjs.map} +1 -1
  75. package/dist/{impl-B_CRtRo8.mjs → impl-D0cf-epj.mjs} +2 -2
  76. package/dist/{impl-B_CRtRo8.mjs.map → impl-D0cf-epj.mjs.map} +1 -1
  77. package/dist/{impl-HnrcGt_T.mjs → impl-D8YS5Bge.mjs} +2 -2
  78. package/dist/{impl-HnrcGt_T.mjs.map → impl-D8YS5Bge.mjs.map} +1 -1
  79. package/dist/{impl-2sYR_UFO.mjs → impl-DCF3CF3O.mjs} +2 -2
  80. package/dist/{impl-2sYR_UFO.mjs.map → impl-DCF3CF3O.mjs.map} +1 -1
  81. package/dist/{impl-Dr9Jezjh2.mjs → impl-DE_cRfz52.mjs} +2 -2
  82. package/dist/{impl-Dr9Jezjh2.mjs.map → impl-DE_cRfz52.mjs.map} +1 -1
  83. package/dist/{impl-DldrBJEa.mjs → impl-DNRA5i0w.mjs} +2 -2
  84. package/dist/{impl-DldrBJEa.mjs.map → impl-DNRA5i0w.mjs.map} +1 -1
  85. package/dist/{impl-DUg1sDwg.mjs → impl-DSPxdc-T.mjs} +2 -2
  86. package/dist/{impl-DUg1sDwg.mjs.map → impl-DSPxdc-T.mjs.map} +1 -1
  87. package/dist/{impl-DzNEfRoS2.mjs → impl-DbddBTRj2.mjs} +2 -2
  88. package/dist/{impl-DzNEfRoS2.mjs.map → impl-DbddBTRj2.mjs.map} +1 -1
  89. package/dist/{impl-BTvZxws5.mjs → impl-DdUe8ClA.mjs} +2 -2
  90. package/dist/{impl-BTvZxws5.mjs.map → impl-DdUe8ClA.mjs.map} +1 -1
  91. package/dist/{impl-CFB_508q.mjs → impl-Dl8xSx8K.mjs} +2 -2
  92. package/dist/{impl-CFB_508q.mjs.map → impl-Dl8xSx8K.mjs.map} +1 -1
  93. package/dist/{impl-DtTfsHtL.mjs → impl-DqnzZv43.mjs} +2 -2
  94. package/dist/{impl-DtTfsHtL.mjs.map → impl-DqnzZv43.mjs.map} +1 -1
  95. package/dist/{impl-Dmn1CWts.mjs → impl-DrlfmH8g.mjs} +2 -2
  96. package/dist/{impl-Dmn1CWts.mjs.map → impl-DrlfmH8g.mjs.map} +1 -1
  97. package/dist/{impl-fg0UZYw6.mjs → impl-F2lRJEqw.mjs} +2 -2
  98. package/dist/{impl-fg0UZYw6.mjs.map → impl-F2lRJEqw.mjs.map} +1 -1
  99. package/dist/{impl-BRFL8xp7.mjs → impl-LDY2dgtc.mjs} +2 -2
  100. package/dist/{impl-BRFL8xp7.mjs.map → impl-LDY2dgtc.mjs.map} +1 -1
  101. package/dist/{impl-S90NUBGm.mjs → impl-OXGlZyUo.mjs} +2 -2
  102. package/dist/{impl-S90NUBGm.mjs.map → impl-OXGlZyUo.mjs.map} +1 -1
  103. package/dist/{impl-CVUD-7mJ.mjs → impl-agm4lpyq.mjs} +2 -2
  104. package/dist/{impl-CVUD-7mJ.mjs.map → impl-agm4lpyq.mjs.map} +1 -1
  105. package/dist/{impl-BzIyVAg_.mjs → impl-f59q7WnK.mjs} +2 -2
  106. package/dist/{impl-BzIyVAg_.mjs.map → impl-f59q7WnK.mjs.map} +1 -1
  107. package/dist/{impl-b4KAEVci.mjs → impl-pA1b-G6B.mjs} +2 -2
  108. package/dist/{impl-b4KAEVci.mjs.map → impl-pA1b-G6B.mjs.map} +1 -1
  109. package/dist/{impl-BVcy_GYa.mjs → impl-tYHONQMu.mjs} +2 -2
  110. package/dist/{impl-BVcy_GYa.mjs.map → impl-tYHONQMu.mjs.map} +1 -1
  111. package/dist/{impl-BD03bzB-.mjs → impl-u1nIo2-E.mjs} +2 -2
  112. package/dist/{impl-BD03bzB-.mjs.map → impl-u1nIo2-E.mjs.map} +1 -1
  113. package/dist/index.d.mts +6 -2
  114. package/dist/index.d.mts.map +1 -1
  115. package/dist/index.mjs +1 -1
  116. package/dist/{inquirer-BU5IpgA8.mjs → inquirer-jzIQWAvq.mjs} +2 -2
  117. package/dist/{inquirer-BU5IpgA8.mjs.map → inquirer-jzIQWAvq.mjs.map} +1 -1
  118. package/dist/{markRequestDataSiloIdsCompleted-C6lV_My-.mjs → markRequestDataSiloIdsCompleted-B-Dgme2r.mjs} +2 -2
  119. package/dist/{markRequestDataSiloIdsCompleted-C6lV_My-.mjs.map → markRequestDataSiloIdsCompleted-B-Dgme2r.mjs.map} +1 -1
  120. package/dist/{markSilentPrivacyRequests-D3bFOMLh.mjs → markSilentPrivacyRequests-CPU4Q2lF.mjs} +2 -2
  121. package/dist/{markSilentPrivacyRequests-D3bFOMLh.mjs.map → markSilentPrivacyRequests-CPU4Q2lF.mjs.map} +1 -1
  122. package/dist/{notifyPrivacyRequestsAdditionalTime-BGUxsWLN.mjs → notifyPrivacyRequestsAdditionalTime-BWsm5Y5K.mjs} +2 -2
  123. package/dist/{notifyPrivacyRequestsAdditionalTime-BGUxsWLN.mjs.map → notifyPrivacyRequestsAdditionalTime-BWsm5Y5K.mjs.map} +1 -1
  124. package/dist/{policy-bundle-manifest-RgsjPnfI.mjs → policy-bundle-manifest-BDey6EBs.mjs} +2 -2
  125. package/dist/{policy-bundle-manifest-RgsjPnfI.mjs.map → policy-bundle-manifest-BDey6EBs.mjs.map} +1 -1
  126. package/dist/{policy-project-discovery-B4cFXUlA.mjs → policy-project-discovery-DV5PnFJq.mjs} +2 -2
  127. package/dist/{policy-project-discovery-B4cFXUlA.mjs.map → policy-project-discovery-DV5PnFJq.mjs.map} +1 -1
  128. package/dist/{policy-scaffold-config-CwlygiTz.mjs → policy-scaffold-config-DtRELUZx.mjs} +2 -2
  129. package/dist/{policy-scaffold-config-CwlygiTz.mjs.map → policy-scaffold-config-DtRELUZx.mjs.map} +1 -1
  130. package/dist/{pullChunkedCustomSiloOutstandingIdentifiers-BfNNWqeI.mjs → pullChunkedCustomSiloOutstandingIdentifiers-BaHp-jxs.mjs} +2 -2
  131. package/dist/{pullChunkedCustomSiloOutstandingIdentifiers-BfNNWqeI.mjs.map → pullChunkedCustomSiloOutstandingIdentifiers-BaHp-jxs.mjs.map} +1 -1
  132. package/dist/{pullManualEnrichmentIdentifiersToCsv-B_lWa6Yg.mjs → pullManualEnrichmentIdentifiersToCsv-CiEFIvl_.mjs} +2 -2
  133. package/dist/{pullManualEnrichmentIdentifiersToCsv-B_lWa6Yg.mjs.map → pullManualEnrichmentIdentifiersToCsv-CiEFIvl_.mjs.map} +1 -1
  134. package/dist/{pushCronIdentifiersFromCsv-CDhlEf6n.mjs → pushCronIdentifiersFromCsv-DeS78zyp.mjs} +2 -2
  135. package/dist/{pushCronIdentifiersFromCsv-CDhlEf6n.mjs.map → pushCronIdentifiersFromCsv-DeS78zyp.mjs.map} +1 -1
  136. package/dist/{pushManualEnrichmentIdentifiersFromCsv-Bn_028w5.mjs → pushManualEnrichmentIdentifiersFromCsv-B3iVwuvs.mjs} +2 -2
  137. package/dist/{pushManualEnrichmentIdentifiersFromCsv-Bn_028w5.mjs.map → pushManualEnrichmentIdentifiersFromCsv-B3iVwuvs.mjs.map} +1 -1
  138. package/dist/{removeUnverifiedRequestIdentifiers-DcI9ut4q.mjs → removeUnverifiedRequestIdentifiers-qS5A_eRZ.mjs} +2 -2
  139. package/dist/{removeUnverifiedRequestIdentifiers-DcI9ut4q.mjs.map → removeUnverifiedRequestIdentifiers-qS5A_eRZ.mjs.map} +1 -1
  140. package/dist/{retryRequestDataSilos-ZdY2PM0S.mjs → retryRequestDataSilos-Cxaka-kA.mjs} +2 -2
  141. package/dist/{retryRequestDataSilos-ZdY2PM0S.mjs.map → retryRequestDataSilos-Cxaka-kA.mjs.map} +1 -1
  142. package/dist/{skipPreflightJobs-KdcSw8Td.mjs → skipPreflightJobs-FdIm_Imj.mjs} +2 -2
  143. package/dist/{skipPreflightJobs-KdcSw8Td.mjs.map → skipPreflightJobs-FdIm_Imj.mjs.map} +1 -1
  144. package/dist/{skipRequestDataSilos-Cy2NfP47.mjs → skipRequestDataSilos-MTbj-OfW.mjs} +2 -2
  145. package/dist/{skipRequestDataSilos-Cy2NfP47.mjs.map → skipRequestDataSilos-MTbj-OfW.mjs.map} +1 -1
  146. package/dist/{streamPrivacyRequestsToCsv-ByRBG8Mb.mjs → streamPrivacyRequestsToCsv-BVzYSXNY.mjs} +2 -2
  147. package/dist/{streamPrivacyRequestsToCsv-ByRBG8Mb.mjs.map → streamPrivacyRequestsToCsv-BVzYSXNY.mjs.map} +1 -1
  148. package/dist/{updateConsentManagerVersionToLatest-KwMaAkwh.mjs → updateConsentManagerVersionToLatest-Udi7Iazb.mjs} +2 -2
  149. package/dist/{updateConsentManagerVersionToLatest-KwMaAkwh.mjs.map → updateConsentManagerVersionToLatest-Udi7Iazb.mjs.map} +1 -1
  150. package/dist/{uploadConsents-C5LmI-9T.mjs → uploadConsents-w7FE65Zg.mjs} +2 -2
  151. package/dist/{uploadConsents-C5LmI-9T.mjs.map → uploadConsents-w7FE65Zg.mjs.map} +1 -1
  152. package/dist/uploadPrivacyRequestsFromCsv-B__QsguT.mjs +2 -0
  153. package/dist/uploadPrivacyRequestsFromCsv-B__QsguT.mjs.map +1 -0
  154. package/package.json +2 -2
  155. package/dist/constants-qvGk0wlN.mjs +0 -2
  156. package/dist/constants-qvGk0wlN.mjs.map +0 -1
  157. package/dist/uploadPrivacyRequestsFromCsv-BsYnCgbl.mjs +0 -2
  158. package/dist/uploadPrivacyRequestsFromCsv-BsYnCgbl.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"policy-project-discovery-B4cFXUlA.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-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":"g6CCMA,MAAa,EAAqCA,EAGrC,EAA6B,SAS7B,EAAsB,UAGtB,EAAkC,GAAG,EAAoB,SAGzD,EAA+B,oBAa/B,EAA2B,YAQ3B,EAAiC,cAGjC,EAAyC,eAGzC,EAAiB,CAC5B,QAAS,UACT,YAAa,cACd,CAGY,EAAwB,CAAC,EAAe,QAAS,EAAe,YAAY,CAwBzF,SAAgB,EACd,EACA,EAA+B,EAAe,QACtC,CACR,MAAO;;;eAGM,EAAK;;;OAGb,EAAuC;qBACzB,EAAS;4BACsBC,EAAY;;;;EAQxB,EACtC,EACA,EAAe,QAChB,CAWD,SAAgB,EAA+B,EAAkC,CAK/E,MAAO;;;;;;gBAMO,EAA2B;;EATvC,EAAM,SAAW,EACb,cACA,aAAa,EAAM,IAAK,GAAS,SAAS,IAAO,CAAC,KAAK;EAAK,GASrD;;EAgBf,SAAgB,EACd,EACA,EACuC,CAEvC,GADgB,EAAiB,MACtB,CAAC,SAAW,EAAG,CACxB,IAAM,EAAQ,CAAC,EAAU,CACzB,MAAO,CAAE,SAAU,EAA+B,EAAM,CAAE,QAAO,CAGnE,IAAI,EACJ,GAAI,CACF,EAAS,EAAK,KAAK,EAAiB,OAC7B,EAAO,CACd,IAAM,EAAS,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CACrE,MAAU,MAAM,yCAAyC,IAAS,CAGpE,GAAI,GAAW,KAA8B,CAC3C,IAAM,EAAQ,CAAC,EAAU,CACzB,MAAO,CAAE,SAAU,EAA+B,EAAM,CAAE,QAAO,CAEnE,GAAI,OAAO,GAAW,UAAY,MAAM,QAAQ,EAAO,CACrD,MAAU,MAAM,8DAA8D,CAGhF,IAAM,EAAS,CAAE,GAAI,EAAoC,CACnD,EAAkB,EAAO,QAC/B,GACE,GACoB,OACnB,OAAO,GAAoB,UAAY,MAAM,QAAQ,EAAgB,EAEtE,MAAU,MAAM,kEAAkE,CAGpF,IAAM,EAAmC,CACvC,GAAK,GAA2D,EAAE,CACnE,CACK,EAAgB,EAAQ,MAC1B,EACJ,GAAI,IAAkB,IAAA,GACpB,EAAQ,CAAC,EAAU,SAEnB,MAAM,QAAQ,EAAc,EAC5B,EAAc,MAAO,GAAyB,OAAO,GAAS,SAAS,CAEvE,EAAQ,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAe,EAAU,CAAC,CAAC,CAAC,MAAM,EAAM,IAC9D,EAAK,cAAc,EAAM,CAC1B,MAED,MAAU,MACR,4HAED,CAGH,EAAQ,MAAQ,EACZ,EAAQ,kBAAoB,IAAA,KAC9B,EAAQ,gBAAkB,GAE5B,EAAO,QAAU,EAEb,EAAO,eAAiB,IAAA,KAC1B,EAAO,aAAe,CACpB,KAAM,CACJ,OAAQ,MACR,QAAS,IAAI,IACd,CACF,EAGH,IAAM,EAAS,EAAK,KAAK,EAAQ,CAC/B,UAAW,GACX,OAAQ,GACR,SAAU,GACX,CAAC,CACF,MAAO,CACL,SAAU,EAAO,SAAS;EAAK,CAAG,EAAS,GAAG,EAAO,IACrD,QACD,CASyC,EAA+B,CAAC,EAAoB,CAAC,CAAjG,MAGa,EAAqC,EAA+B,EAAE,CAAC,CAGzC,GAOjC,EAPiC,EAyBK,GAAW,EAAX,EAElC,EAFkC,EA/BhD,MAqDa,EAAgC;;;;;EAQD,GAEI,EAFJ,EA7D5C,MAoFa,EAA4B;;EAK5B,EAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAqD3B,EAA+B;iBACzB,EAA+B;;;yCAGP,EAA+B;mBACrD,EAA+B;6BACrB,EAA+B;8BAC9B,EAA+B;;;;;;;MAOvD,EAA+B;2BACV,EAA+B;;;;;;;;EAWX,GAAG,EAAH,EAAsC,EAAtC,EAGK,GAAG,EAAH,EAAsC,EAAtC,EAK1C,GAAG,EAAH,EAAsC,EAAtC,EAOO,GAAqC,EAArC,EAGP,GAAG,EAAH,EAAsC,EAAtC,EAeA,GAAG,EAAH,EAKA,GAAG,EAAH,EAyBV,MAAM,EAA4D,CAChE,CACE,KAAM,qBACN,SAAU,EACV,YAAa,qCAAqC,EAA2B,eAC9E,CACD,CACE,KAAM,YACN,SAAU,EACV,YAAa,kDACd,CACF,CAOD,SAAgB,GAAoD,CAClE,OAAO,EAA4B,IAAK,IAAU,CAAE,GAAG,EAAM,EAAE,CAQjE,MAAa,EAAoE,EAC9E,EAAe,SAAU,mBACzB,EAAe,aAAc,yBAC/B,CAGY,EAAoE,EAC9E,EAAe,SAAU,WACzB,EAAe,aAAc,EAC/B,CAQD,SAAgB,EAAyB,EAAsB,CAC7D,MAAO,GAAG,EAAK,SASjB,SAAgB,EAA4B,EAA8B,CAaxE,OAZK,EAGD,IAAU,KAAO,IAAU,MAAQ,EAAM,SAAS,IAAI,EAAI,EAAM,SAAS,KAAK,CACzE,+DAEJ,gCAAgC,KAAK,EAAM,CAG5C,EAAM,OAAS,IACV,iDAEF,GALE,0EANA,iCAqBX,SAAgB,EACd,EACA,EAAoB,EAAyB,EAAK,CAC7B,CACrB,IAAM,EAAS,EACf,MAAO,CACL,CACE,KAAM,GAAG,EAAO,GAAG,IACnB,SAAU,EAA4B,EAAM,EAAe,QAAQ,CACnE,YAAa,kCACd,CACD,CACE,KAAM,GAAG,EAAO,GAAG,IACnB,SAAU;;gDAEgC,EAAK;cACvC,IAAS,UAAY,uBAAyB,GAAG,EAAK,eAAe;oBAC/D,IAAS,UAAY,uDAAyD,0BAA0B,EAAK,UAAU;;;;;;;;;;2CAUhG,IAAS,UAAY,UAAY,EAAK;;;;;;EAO3E,YAAa,wCAAwC,EAAK,SAC3D,CACD,CACE,KAAM,GAAG,EAAO,GAAG,EAAK,qBACxB,SAAU;WACL,IAAS,UAAY,4BAA8B,GAAG,EAAK,SAAS;;oBAE3D,IAAS,UAAY,YAAc,GAAG,2BAA2B,IAAS,UAAY,WAAa,GAAG;;;;UAIhH,EAAK;;;;;;;;;;;;;;;EAgBT,YAAa,oDACd,CACD,CACE,KAAM,GAAG,EAAO,GAAG,EAAK,0BACxB,SAAU,WAAW,EAAK;;cAElB,EAAK;;;;;;;;;;;;;;;;;EAkBb,YAAa,8DACd,CACD,CACE,KAAM,GAAG,EAAO,qBAChB,SAAU,EACV,YAAa,oDACd,CACD,CACE,KAAM,GAAG,EAAO,aAChB,SAAU,EACV,YAAa,mEACd,CACD,CACE,KAAM,GAAG,EAAO,aAChB,SAAU,EACV,YAAa,2CACd,CACF,CAiBH,SAAgB,GAA8C,CAC5D,MAAO,GAAG,KAAK,UAAUC,EAA8B,KAAM,EAAE,CAAC,IAQlE,SAAgB,GAA+C,CAC7D,GAAM,CAAC,GAAWC,EAClB,GAAI,CAAC,EACH,MAAU,MAAM,kEAAkE,CAEpF,MAAO,GAAG,KAAK,UAAU,EAAS,KAAM,EAAE,CAAC,IAU7C,SAAgB,EACd,EACA,EAAoB,EAAyB,EAAK,CAC7B,CACrB,IAAM,EAAS,EACT,EAAe,GAAsC,CAC3D,MAAO,CACL,CACE,KAAM,GAAG,EAAO,GAAG,IACnB,SAAU,EAA4B,EAAM,EAAe,YAAY,CACvE,YAAa,kCACd,CACD,CACE,KAAM,GAAG,EAAO,GAAG,IACnB,SAAU,GAAqC,CAC/C,YAAa,wCAAwC,EAAK,wDAC3D,CACD,CACE,KAAM,GAAG,EAAO,GAAG,EAAK,qBACxB,SAAU;;;;UAIN,EAAK;;;EAIT,YAAa,iDACd,CACD,CACE,KAAM,GAAG,EAAO,GAAG,EAAK,mBACxB,SAAU;;;;;;;;EASV,YAAa,+CACd,CACD,CACE,KAAM,GAAG,EAAO,GAAG,EAAK,qCACxB,SAAU;;;;;;UAMN,EAAK;;;;cAID,EAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyCb,YAAa,+CACd,CACD,CACE,KAAM,GAAG,EAAO,GAAG,EAAK,0CACxB,SAAU;;UAEN,EAAK;;;;cAID,EAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuFb,YAAa,qCACd,CACD,CACE,KAAM,GAAG,EAAO,GAAG,EAAK,oCACxB,SAAU;;;;cAIF,EAAK;UACT,EAAK;;;;cAID,EAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4Bb,YAAa,6CACd,CACD,CACE,KAAM,GAAG,EAAO,GAAG,EAAK,yCACxB,SAAU;UACN,EAAK;;;;cAID,EAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsDb,YAAa,0CACd,CACD,CACE,KAAM,GAAG,EAAO,GAAG,EAAK,2BACxB,SAAU;;;;;UAKN,EAAK;;;EAIT,YAAa,iCACd,CACD,CACE,KAAM,GAAG,EAAO,qBAChB,SAAU,EACV,YAAa,0DACd,CACD,CACE,KAAM,GAAG,EAAO,aAChB,SAAU,EACV,YAAa,mEACd,CACD,CACE,KAAM,GAAG,EAAO,aAChB,SAAU,EACV,YAAa,2CACd,CACF,CAWH,SAAgB,EACd,EACA,EACA,EAAoB,EAAyB,EAAK,CAC7B,CACrB,OAAQ,EAAR,CACE,KAAK,EAAe,QAClB,OAAO,EAA2B,EAAM,EAAU,CACpD,KAAK,EAAe,YAClB,OAAO,EAA+B,EAAM,EAAU,CACxD,QACE,MAAU,MAAM,4BAA4B,OAAO,EAAS,GAAG,EAUrE,SAAgB,EAA8B,EAOnC,CACT,GAAM,CAAE,kBAAiB,OAAM,YAAa,EACtC,EACJ,IAAa,EAAe,YAAc,QAAQ,EAAK,WAAa,QAAQ,EAAK,SACnF,MAAO,CACL,yBAAyB,EAAgB,KACzC,eAAe,EAAY,KAC3B,aAAa,EAAgB,gBAC7B,cAAc,EAAgB,GAAG,IAClC,CAAC,KAAK;EAAK,CCphCd,MAAa,EAAmC,mBAShD,SAAgB,EACd,EACA,EAAoB,EACZ,CACR,OAAO,EAAQ,EAAqB,EAAU,CA0BhD,SAAgB,EACd,EACA,EACU,CACV,GAAI,CAAC,EAAQ,GAAG,WAAW,EAAU,EAAI,CAAC,EAAQ,GAAG,SAAS,EAAU,CAAC,aAAa,CACpF,MAAO,EAAE,CAEX,IAAM,EAAiB,EAAK,EAAW,EAAyB,CAIhE,OAHI,EAAQ,GAAG,WAAW,EAAe,EAAI,EAAQ,GAAG,SAAS,EAAe,CAAC,QAAQ,CAChF,CAAC,EAAU,CAEb,EAAQ,GACZ,YAAY,EAAW,CAAE,cAAe,GAAM,CAAC,CAC/C,OAAQ,GAAU,EAAM,aAAa,CAAC,CACtC,IAAK,GAAU,EAAK,EAAW,EAAM,KAAK,CAAC,CAC3C,OAAQ,GAAoB,CAC3B,IAAM,EAAe,EAAK,EAAiB,EAAyB,CACpE,OAAO,EAAQ,GAAG,WAAW,EAAa,EAAI,EAAQ,GAAG,SAAS,EAAa,CAAC,QAAQ,EACxF,CACD,MAAM,EAAM,IAAU,EAAK,cAAc,EAAM,CAAC,CAUrD,SAAgB,EACd,EACA,EAAoB,EACA,CACpB,IAAM,EAAsB,EAAQ,QAAQ,KAAK,CAC3C,EAAkB,EAA8B,EAAqB,EAAU,CACrF,GACE,EAAQ,GAAG,WAAW,EAAgB,EACtC,CAAC,EAAQ,GAAG,SAAS,EAAgB,CAAC,aAAa,CAEnD,MAAU,MAAM,qCAAqC,IAAkB,CAOzE,MAAO,CACL,sBACA,kBACA,GARiB,EAA0B,EAAS,CACpD,sBACA,kBACA,sBAAuB,EACxB,CAIc,CACb,cAAe,EAA4B,EAAS,EAAgB,CACpE,kBAAmB,EAA4B,EAAS,EAAiB,CACvE,mBAAoB,GACrB,CAAC,CACH"}
@@ -1,4 +1,4 @@
1
- import{_ as e,c as t,d as n,f as r,g as i,o as a,s as o,u as s}from"./policy-project-discovery-B4cFXUlA.mjs";import{n as c,r as l,t as u}from"./jsonc-CdTubr53.mjs";import{posix as d,relative as f,sep as p}from"node:path";const m=`0.42.0`,h=`.github/workflows/transcend-policy.yml`,g=`d23441a48e516b6c34aea4fa41551a30e30af803`,_=`b2b258e089860efaadaaf71bf6e3aecb4a3eeff1`,v=`761188c3b435761fa254beca508a44875619648f`;function y(e,t){return e===`.`?t:d.join(e,t)}function b(e){let t=e.bundleDirectories??[y(e.workspaceDirectory,s)],r=[y(e.workspaceDirectory,`**/*.rego`),y(e.workspaceDirectory,`**/*.json`),y(e.workspaceDirectory,`**/*.yaml`),y(e.workspaceDirectory,`**/*.yml`),y(e.workspaceDirectory,`**/.manifest`),y(e.workspaceDirectory,`.regal/config.yaml`),y(e.workspaceDirectory,`.regal.yaml`),h].map(e=>` - ${JSON.stringify(e)}`).join(`
1
+ import{_ as e,c as t,d as n,f as r,g as i,o as a,s as o,u as s}from"./policy-project-discovery-DV5PnFJq.mjs";import{n as c,r as l,t as u}from"./jsonc-CdTubr53.mjs";import{posix as d,relative as f,sep as p}from"node:path";const m=`0.42.0`,h=`.github/workflows/transcend-policy.yml`,g=`d23441a48e516b6c34aea4fa41551a30e30af803`,_=`b2b258e089860efaadaaf71bf6e3aecb4a3eeff1`,v=`761188c3b435761fa254beca508a44875619648f`;function y(e,t){return e===`.`?t:d.join(e,t)}function b(e){let t=e.bundleDirectories??[y(e.workspaceDirectory,s)],r=[y(e.workspaceDirectory,`**/*.rego`),y(e.workspaceDirectory,`**/*.json`),y(e.workspaceDirectory,`**/*.yaml`),y(e.workspaceDirectory,`**/*.yml`),y(e.workspaceDirectory,`**/.manifest`),y(e.workspaceDirectory,`.regal/config.yaml`),y(e.workspaceDirectory,`.regal.yaml`),h].map(e=>` - ${JSON.stringify(e)}`).join(`
2
2
  `),i;return i=t.length===0?` lint:
3
3
  runs-on: ubuntu-latest
4
4
  steps:
@@ -73,4 +73,4 @@ ${i}`}const x=`tsandall.opa`,S=`glebbash.opa-highlight-only`;function C(t,n){ret
73
73
  `,warning:`${t instanceof Error?t.message:String(t)} Existing content was left unchanged.`}}}function E(e,t,n,r){if(t.length===0)return{contents:e??`{}
74
74
  `,warnings:r};try{return{contents:u(e,t,n),warnings:r}}catch(t){return{contents:e??`{}
75
75
  `,warnings:[...r,`${t instanceof Error?t.message:String(t)} Existing content was left unchanged.`]}}}function D(e,t,n,r){t.forEach(({path:t,value:i,label:a})=>{let o=t.reduce((e,t)=>e&&typeof e==`object`&&!Array.isArray(e)?e[t]:void 0,e);o===void 0?n.push({path:t,value:i}):w(o,i)||r.push(`VS Code setting "${a}" has a repository-specific value and was left unchanged.`)})}function O(e){return e===`.`?"${workspaceFolder}":`\${workspaceFolder}/${e}`}function k(e,t){return f(e,t).split(p).join(`/`)||`.`}function A(e,t,n=C(r)){let i=k(e,t);return O(i===`.`?n.bundleDir:`${i}/${n.bundleDir}`)}function j(e,n){let r=k(e,n);return r===`.`?`**/${t}`:`**/${r}/**/${t}`}function M(e,t,n=C(r)){let s=k(e,t),c=s===`.`?`/${n.bundleDir}`:`/${s}/${n.bundleDir}`,l=n.template===i.Permissions?a:s===`.`?`./${n.bundleDir}/${o}`:`./${s}/${n.bundleDir}/${o}`;return{fileMatch:[`${c}/input.json`,`${c}/input.example.json`],url:l}}function N(e,t,n,r){let i=T(e,`VS Code policy settings`);if(`warning`in i)return{contents:i.contents,warnings:[i.warning]};let{current:a}=i,o=[],s=[];if(r&&r.length>0){let e=a[`opa.roots`],i=r.map(e=>A(t,n,e));if(e===void 0)o.push({path:[`opa.roots`],value:i});else if(Array.isArray(e)&&e.every(e=>typeof e==`string`)){let t=c(e,i);w(t,e)||o.push({path:[`opa.roots`],value:t})}else s.push(`VS Code setting "opa.roots" is customized and was left unchanged.`)}D(a,[{path:[`opa.checkOnSave`],value:!0,label:`opa.checkOnSave`},{path:[`opa.strictMode`],value:!0,label:`opa.strictMode`},{path:[`opa.bundleMode`],value:!0,label:`opa.bundleMode`},{path:[`opa.formatter`],value:`opa-fmt-rego-v1`,label:`opa.formatter`}],o,s);let l=a[`files.associations`],u=j(t,n);if(l===void 0?o.push({path:[`files.associations`],value:{[u]:`json`}}):typeof l==`object`&&l&&!Array.isArray(l)?D(a,[{path:[`files.associations`,u],value:`json`,label:`files.associations."${u}"`}],o,s):s.push(`VS Code setting "files.associations" is customized and was left unchanged.`),r&&r.length>0){let e=r.map(e=>M(t,n,e)),i=a[`json.schemas`];if(i===void 0)o.push({path:[`json.schemas`],value:e});else if(Array.isArray(i)){let t=[...i];e.forEach(e=>{if(t.some(t=>typeof t==`object`&&!!t&&!Array.isArray(t)&&w(t,e)))return;let n=t.findIndex(t=>typeof t==`object`&&!!t&&!Array.isArray(t)&&typeof t.url==`string`&&t.url===e.url);if(n===-1){t=[...t,e];return}let r=t[n],i=r.fileMatch;if(!Array.isArray(i)||!i.every(e=>typeof e==`string`)){s.push(`VS Code setting "json.schemas" has a repository-specific entry for the ${e.url} schema and was left unchanged.`);return}let a=c(i,e.fileMatch);w(a,i)||(t=t.map((e,t)=>t===n?{...r,fileMatch:a}:e))}),w(t,i)||o.push({path:[`json.schemas`],value:t})}else s.push(`VS Code setting "json.schemas" is customized and was left unchanged.`)}let d=a[`[rego]`];return d===void 0?o.push({path:[`[rego]`],value:{"editor.defaultFormatter":x,"editor.formatOnSave":!0,"editor.insertSpaces":!1,"editor.tabSize":4}}):typeof d==`object`&&d&&!Array.isArray(d)?D(a,[{path:[`[rego]`,`editor.defaultFormatter`],value:x,label:`[rego].editor.defaultFormatter`},{path:[`[rego]`,`editor.formatOnSave`],value:!0,label:`[rego].editor.formatOnSave`},{path:[`[rego]`,`editor.insertSpaces`],value:!1,label:`[rego].editor.insertSpaces`},{path:[`[rego]`,`editor.tabSize`],value:4,label:`[rego].editor.tabSize`}],o,s):s.push(`VS Code language setting "[rego]" is customized and was left unchanged.`),E(e,o,`VS Code policy settings`,s)}function P(e){let t=T(e,`VS Code policy extension recommendations`);if(`warning`in t)return{contents:t.contents,warnings:[t.warning]};let{current:n}=t,r=[],i=[],a=n.recommendations;if(a===void 0||Array.isArray(a)&&a.every(e=>typeof e==`string`)){let e=a??[],t=c(e,[x]).filter(e=>e!==S);w(e,t)||r.push({path:[`recommendations`],value:t})}else i.push(`VS Code extension recommendations use an unknown shape and were left unchanged.`);let o=n.unwantedRecommendations;if(o===void 0||Array.isArray(o)&&o.every(e=>typeof e==`string`)){let e=o??[],t=c(e,[S]);w(e,t)||r.push({path:[`unwantedRecommendations`],value:t})}else i.push(`VS Code unwanted extension recommendations use an unknown shape and were left unchanged.`);return E(e,r,`VS Code policy extension recommendations`,i)}function F(e,t,n=C(r)){let i=k(e,t),a=i===`.`?n.bundleDir:`${i}/${n.bundleDir}`;return{label:`policy: lint ${n.root}`,type:`process`,command:`transcend`,args:[`policy`,`lint`,a,`--noInteractive`],group:{kind:`test`,isDefault:!1},problemMatcher:[]}}function I(e){return{label:`policy: lint`,dependsOn:e.map(e=>`policy: lint ${e.root}`),dependsOrder:`sequence`,group:{kind:`test`,isDefault:!0},problemMatcher:[]}}function L(e,t,n,r){let i=T(e,`VS Code policy tasks`);if(`warning`in i)return{contents:i.contents,warnings:[i.warning]};let{current:a}=i,o=[],s=[],c=a.version;if(c===void 0?o.push({path:[`version`],value:`2.0.0`}):c!==`2.0.0`&&s.push(`VS Code task schema version is customized and was left unchanged.`),!r||r.length===0)return E(e,o,`VS Code policy tasks`,s);let l=r.map(e=>F(t,n,e)),u=I(r),d=[...l,u],f=new Set(d.map(e=>e.label)),p=a.tasks;if(p===void 0)o.push({path:[`tasks`],value:d});else if(Array.isArray(p)){let e=[...p],t=!1;d.forEach(n=>{let r=n.label,i=e.findIndex(e=>typeof e==`object`&&!!e&&!Array.isArray(e)&&e.label===r);if(i===-1){e=[...e,n],t=!0;return}w(e[i],n)||(f.has(r)?(e=e.map((e,t)=>t===i?n:e),t=!0):s.push(`VS Code task "${r}" has repository-specific customization and was left unchanged.`))}),t&&o.push({path:[`tasks`],value:e})}else s.push(`VS Code tasks use an unknown shape and were left unchanged.`);return E(e,o,`VS Code policy tasks`,s)}export{h as a,C as i,N as n,b as o,L as r,P as t};
76
- //# sourceMappingURL=policy-scaffold-config-CwlygiTz.mjs.map
76
+ //# sourceMappingURL=policy-scaffold-config-DtRELUZx.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"policy-scaffold-config-CwlygiTz.mjs","names":[],"sources":["../src/lib/policy/policy-scaffold-artifacts.ts","../src/lib/policy/policy-scaffold-config.ts"],"sourcesContent":["import { posix } from 'node:path';\n\nimport {\n POLICY_STARTER_BUNDLE_DIRECTORY,\n POLICY_STARTER_OPA_VERSION,\n} from './policy-scaffold-templates.js';\n\n/** Regal version pinned by generated policy validation. */\nexport const POLICY_STARTER_REGAL_VERSION = '0.42.0';\n\n/** Repository-relative generated workflow path. */\nexport const POLICY_CI_WORKFLOW_PATH = '.github/workflows/transcend-policy.yml';\n\n/** Immutable actions/checkout v6 release commit. */\nexport const ACTIONS_CHECKOUT_SHA = 'd23441a48e516b6c34aea4fa41551a30e30af803';\n\n/** Immutable open-policy-agent/setup-opa v2.4.0 release commit. */\nexport const SETUP_OPA_SHA = 'b2b258e089860efaadaaf71bf6e3aecb4a3eeff1';\n\n/** Immutable open-policy-agent/setup-regal v2.0.0 release commit. */\nexport const SETUP_REGAL_SHA = '761188c3b435761fa254beca508a44875619648f';\n\n/**\n * Prefix a path below the repository-relative policy workspace.\n *\n * @param workspaceDirectory - POSIX repository-relative workspace\n * @param child - POSIX workspace-relative path or pattern\n * @returns Repository-relative path or pattern\n */\nfunction workspacePath(workspaceDirectory: string, child: string): string {\n return workspaceDirectory === '.' ? child : posix.join(workspaceDirectory, child);\n}\n\n/**\n * Generate credential-free, validation-only Policy Engine CI.\n *\n * Path filters watch the whole policy workspace. Lint runs against each\n * publishable `{root}-bundle/` directory.\n *\n * @param options - Repository paths and exact CLI release\n * @returns Complete GitHub Actions workflow YAML\n */\nexport function generatePolicyGithubActionsWorkflow(options: {\n /** Exact released Transcend CLI version. */\n cliVersion: string;\n /** Policy workspace directory relative to the repository root. */\n workspaceDirectory: string;\n /**\n * Publish directories to lint, relative to the repository root.\n *\n * Pass an empty array for a workspace with no bundles yet (`policy init`).\n * Defaults to the disposable starter bundle under the workspace when omitted.\n */\n bundleDirectories?: readonly string[];\n}): string {\n const bundleDirectories = options.bundleDirectories ?? [\n workspacePath(options.workspaceDirectory, POLICY_STARTER_BUNDLE_DIRECTORY),\n ];\n const watchedPaths = [\n workspacePath(options.workspaceDirectory, '**/*.rego'),\n workspacePath(options.workspaceDirectory, '**/*.json'),\n workspacePath(options.workspaceDirectory, '**/*.yaml'),\n workspacePath(options.workspaceDirectory, '**/*.yml'),\n workspacePath(options.workspaceDirectory, '**/.manifest'),\n workspacePath(options.workspaceDirectory, '.regal/config.yaml'),\n workspacePath(options.workspaceDirectory, '.regal.yaml'),\n POLICY_CI_WORKFLOW_PATH,\n ];\n const pathFilters = watchedPaths.map((path) => ` - ${JSON.stringify(path)}`).join('\\n');\n\n let lintJob: string;\n if (bundleDirectories.length === 0) {\n lintJob = ` lint:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@${ACTIONS_CHECKOUT_SHA} # v6\n - name: No publishable bundles yet\n run: >-\n echo \"Run transcend policy new to add a bundle;\n policy new refreshes this workflow automatically.\"\n`;\n } else if (bundleDirectories.length === 1) {\n lintJob = ` lint:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@${ACTIONS_CHECKOUT_SHA} # v6\n - name: Set up OPA\n uses: open-policy-agent/setup-opa@${SETUP_OPA_SHA} # v2.4.0\n with:\n version: ${JSON.stringify(POLICY_STARTER_OPA_VERSION)}\n - name: Set up Regal\n uses: open-policy-agent/setup-regal@${SETUP_REGAL_SHA} # v2.0.0\n with:\n version: ${JSON.stringify(POLICY_STARTER_REGAL_VERSION)}\n - name: Install the Transcend CLI\n run: npm install --global @transcend-io/cli@${options.cliVersion}\n - name: Validate Policy Engine project\n env:\n POLICY_DIRECTORY: ${JSON.stringify(bundleDirectories[0])}\n run: >-\n transcend policy lint\n \"$POLICY_DIRECTORY\"\n --noInteractive\n --json\n`;\n } else {\n lintJob = ` lint:\n runs-on: ubuntu-latest\n strategy:\n fail-fast: false\n matrix:\n policy_directory:\n${bundleDirectories.map((directory) => ` - ${JSON.stringify(directory)}`).join('\\n')}\n steps:\n - uses: actions/checkout@${ACTIONS_CHECKOUT_SHA} # v6\n - name: Set up OPA\n uses: open-policy-agent/setup-opa@${SETUP_OPA_SHA} # v2.4.0\n with:\n version: ${JSON.stringify(POLICY_STARTER_OPA_VERSION)}\n - name: Set up Regal\n uses: open-policy-agent/setup-regal@${SETUP_REGAL_SHA} # v2.0.0\n with:\n version: ${JSON.stringify(POLICY_STARTER_REGAL_VERSION)}\n - name: Install the Transcend CLI\n run: npm install --global @transcend-io/cli@${options.cliVersion}\n - name: Validate Policy Engine project\n env:\n POLICY_DIRECTORY: \\${{ matrix.policy_directory }}\n run: transcend policy lint \"$POLICY_DIRECTORY\" --noInteractive --json\n`;\n }\n\n return `# Generated by transcend policy init.\nname: Transcend Policy\n\non:\n pull_request:\n paths:\n${pathFilters}\n push:\n paths:\n${pathFilters}\n\npermissions:\n contents: read\n\njobs:\n${lintJob}`;\n}\n","import { relative, sep } from 'node:path';\n\nimport {\n mergeJsonc,\n mergeStringArray,\n parseJsoncObject,\n type JsoncUpdate,\n} from '../scaffolding/jsonc.js';\nimport {\n buildBundleDirectoryName,\n PERMISSIONS_POLICY_INPUT_SCHEMA_ID,\n POLICY_INPUT_SCHEMA_FILENAME,\n POLICY_MANIFEST_FILENAME,\n POLICY_STARTER_ROOT,\n PolicyTemplate,\n type PolicyTemplateName,\n} from './policy-scaffold-templates.js';\n\n/** Recommended VS Code extension for OPA and Regal authoring. */\nexport const POLICY_VSCODE_EXTENSION = 'tsandall.opa';\n\n/** Conflicting syntax-only VS Code extension. */\nexport const CONFLICTING_POLICY_VSCODE_EXTENSION = 'glebbash.opa-highlight-only';\n\n/**\n * Local publish unit: Rego package root plus its workspace-relative folder basename.\n *\n * `bundleDir` is not required to be `{root}-bundle` — `policy new --bundle-dir` may override it.\n */\nexport interface PolicyBundleRef {\n /** Package-path root (`.manifest` / Regal `project.roots`). */\n root: string;\n /** Basename of the publish directory under the policy workspace. */\n bundleDir: string;\n /**\n * Scaffold template that produced this bundle, when known.\n *\n * Permissions bundles point VS Code `json.schemas` at the published input\n * schema `$id` so editor validation tracks the live contract.\n */\n template?: PolicyTemplateName;\n}\n\n/**\n * Build a bundle ref with the default `{root}-bundle` directory.\n *\n * @param root - Package root\n * @param template - Optional scaffold template\n * @returns Bundle ref\n */\nexport function policyBundleRef(root: string, template?: PolicyTemplateName): PolicyBundleRef {\n return template === undefined\n ? { root, bundleDir: buildBundleDirectoryName(root) }\n : { root, bundleDir: buildBundleDirectoryName(root), template };\n}\n\n/** Result of safely merging one repository editor artifact. */\nexport interface PolicyEditorMergeResult {\n /** Complete merged contents, or the unchanged original when unsafe. */\n contents: string;\n /** Actionable preservation warnings. */\n warnings: string[];\n}\n\n/** One desired scalar setting that must not replace a customization. */\ninterface DesiredSetting {\n /** JSONC object path. */\n path: string[];\n /** Desired generated value. */\n value: unknown;\n /** Human-readable setting key. */\n label: string;\n}\n\n/**\n * Compare JSON-compatible configuration values.\n *\n * @param left - Existing value\n * @param right - Desired value\n * @returns Whether the values are structurally equal\n */\nfunction configurationValuesEqual(left: unknown, right: unknown): boolean {\n return JSON.stringify(left) === JSON.stringify(right);\n}\n\n/**\n * Parse an editor object while preserving malformed existing content.\n *\n * @param contents - Existing JSONC, or null\n * @param label - Configuration label\n * @returns Parsed object or a preservation result\n */\nfunction parseEditorObject(\n contents: string | null,\n label: string,\n):\n | {\n /** Parsed JSONC object. */\n current: Record<string, unknown>;\n }\n | {\n /** Complete unchanged contents. */\n contents: string;\n /** Actionable warning. */\n warning: string;\n } {\n try {\n return { current: parseJsoncObject(contents ?? '{}\\n', label) };\n } catch (error) {\n return {\n contents: contents ?? '{}\\n',\n warning: `${error instanceof Error ? error.message : String(error)} Existing content was left unchanged.`,\n };\n }\n}\n\n/**\n * Apply safe JSONC updates or preserve the existing file when comments prevent it.\n *\n * @param contents - Existing JSONC\n * @param updates - Proven-safe updates\n * @param label - Configuration label\n * @param warnings - Existing merge warnings\n * @returns Complete contents and warnings\n */\nfunction applyEditorUpdates(\n contents: string | null,\n updates: readonly JsoncUpdate[],\n label: string,\n warnings: string[],\n): PolicyEditorMergeResult {\n if (updates.length === 0) {\n return { contents: contents ?? '{}\\n', warnings };\n }\n try {\n return {\n contents: mergeJsonc(contents, updates, label),\n warnings,\n };\n } catch (error) {\n return {\n contents: contents ?? '{}\\n',\n warnings: [\n ...warnings,\n `${error instanceof Error ? error.message : String(error)} Existing content was left unchanged.`,\n ],\n };\n }\n}\n\n/**\n * Add absent scalar settings without replacing repository customizations.\n *\n * @param current - Parsed editor object\n * @param desired - Desired settings\n * @param updates - Safe updates to append to\n * @param warnings - Conflict warnings to append to\n */\nfunction collectSafeScalarUpdates(\n current: Record<string, unknown>,\n desired: readonly DesiredSetting[],\n updates: JsoncUpdate[],\n warnings: string[],\n): void {\n desired.forEach(({ path, value, label }) => {\n const existing = path.reduce<unknown>((parent, key) => {\n return parent && typeof parent === 'object' && !Array.isArray(parent)\n ? (parent as Record<string, unknown>)[key]\n : undefined;\n }, current);\n if (existing === undefined) {\n updates.push({ path, value });\n } else if (!configurationValuesEqual(existing, value)) {\n warnings.push(\n `VS Code setting \"${label}\" has a repository-specific value and was left unchanged.`,\n );\n }\n });\n}\n\n/**\n * Build a `${workspaceFolder}/…` path for a repository-relative POSIX path.\n *\n * @param repositoryRelativePath - Path relative to the repository root\n * @returns VS Code workspace path\n */\nfunction buildWorkspaceFolderPath(repositoryRelativePath: string): string {\n return repositoryRelativePath === '.'\n ? '${workspaceFolder}'\n : `\\${workspaceFolder}/${repositoryRelativePath}`;\n}\n\n/**\n * Repository-relative POSIX path from the root that owns `.vscode`.\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param absolutePath - Absolute path below the repository\n * @returns POSIX repository-relative path\n */\nfunction repositoryRelativePosix(repositoryRoot: string, absolutePath: string): string {\n return relative(repositoryRoot, absolutePath).split(sep).join('/') || '.';\n}\n\n/**\n * Build the workspace-relative OPA publish root for a bundle.\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundle - Package root and local directory basename\n * @returns VS Code workspace path\n */\nfunction buildOpaBundleRoot(\n repositoryRoot: string,\n workspaceDirectory: string,\n bundle: PolicyBundleRef = policyBundleRef(POLICY_STARTER_ROOT),\n): string {\n const workspace = repositoryRelativePosix(repositoryRoot, workspaceDirectory);\n const path = workspace === '.' ? bundle.bundleDir : `${workspace}/${bundle.bundleDir}`;\n return buildWorkspaceFolderPath(path);\n}\n\n/**\n * Build a `files.associations` glob for every `.manifest` under the workspace.\n *\n * Patterns that contain `/` are matched against the absolute file path, so a\n * leading `**\\/` prefix is required for a portable workspace-relative association.\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @returns Association pattern key\n */\nfunction buildPolicyManifestAssociationPattern(\n repositoryRoot: string,\n workspaceDirectory: string,\n): string {\n const workspace = repositoryRelativePosix(repositoryRoot, workspaceDirectory);\n return workspace === '.'\n ? `**/${POLICY_MANIFEST_FILENAME}`\n : `**/${workspace}/**/${POLICY_MANIFEST_FILENAME}`;\n}\n\n/**\n * Build `json.schemas` fileMatch globs for a bundle's input documents.\n *\n * Permissions templates use the published schema `$id` so VS Code validation\n * tracks the live contract. Generic bundles use the local `input.schema.json`\n * (OPA / offline tooling still always use the local file via `--schema`).\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundle - Package root and local directory basename\n * @returns Absolute-style fileMatch paths and schema URL\n */\nfunction buildBundleInputJsonSchema(\n repositoryRoot: string,\n workspaceDirectory: string,\n bundle: PolicyBundleRef = policyBundleRef(POLICY_STARTER_ROOT),\n): {\n /** VS Code fileMatch globs (leading slash = workspace-relative). */\n fileMatch: string[];\n /** Schema URL (local path or published Permissions `$id`). */\n url: string;\n} {\n const workspace = repositoryRelativePosix(repositoryRoot, workspaceDirectory);\n const bundlePrefix =\n workspace === '.' ? `/${bundle.bundleDir}` : `/${workspace}/${bundle.bundleDir}`;\n const schemaUrl =\n bundle.template === PolicyTemplate.Permissions\n ? PERMISSIONS_POLICY_INPUT_SCHEMA_ID\n : workspace === '.'\n ? `./${bundle.bundleDir}/${POLICY_INPUT_SCHEMA_FILENAME}`\n : `./${workspace}/${bundle.bundleDir}/${POLICY_INPUT_SCHEMA_FILENAME}`;\n return {\n fileMatch: [`${bundlePrefix}/input.json`, `${bundlePrefix}/input.example.json`],\n url: schemaUrl,\n };\n}\n\n/**\n * Merge strict workspace-scoped OPA and Rego editor settings.\n *\n * Setting names match the authoritative `open-policy-agent/vscode-opa`\n * extension metadata. Existing conflicting values are preserved and reported.\n *\n * When `bundles` is provided, `opa.roots` and `json.schemas` are populated\n * for those bundles. When omitted (init without bundles), `opa.roots` and\n * `json.schemas` are left empty or unset.\n *\n * @param contents - Existing `.vscode/settings.json` JSONC\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundles - Local publish units to add to `opa.roots` and `json.schemas`\n * @returns Merged settings and conflict warnings\n */\nexport function mergePolicyEditorSettings(\n contents: string | null,\n repositoryRoot: string,\n workspaceDirectory: string,\n bundles?: readonly PolicyBundleRef[],\n): PolicyEditorMergeResult {\n const parsed = parseEditorObject(contents, 'VS Code policy settings');\n if ('warning' in parsed) {\n return { contents: parsed.contents, warnings: [parsed.warning] };\n }\n const { current } = parsed;\n const updates: JsoncUpdate[] = [];\n const warnings: string[] = [];\n\n if (bundles && bundles.length > 0) {\n const roots = current['opa.roots'];\n const desiredRoots = bundles.map((bundle) =>\n buildOpaBundleRoot(repositoryRoot, workspaceDirectory, bundle),\n );\n if (roots === undefined) {\n updates.push({ path: ['opa.roots'], value: desiredRoots });\n } else if (Array.isArray(roots) && roots.every((value) => typeof value === 'string')) {\n const merged = mergeStringArray(roots, desiredRoots);\n if (!configurationValuesEqual(merged, roots)) {\n updates.push({ path: ['opa.roots'], value: merged });\n }\n } else {\n warnings.push('VS Code setting \"opa.roots\" is customized and was left unchanged.');\n }\n }\n\n collectSafeScalarUpdates(\n current,\n [\n { path: ['opa.checkOnSave'], value: true, label: 'opa.checkOnSave' },\n { path: ['opa.strictMode'], value: true, label: 'opa.strictMode' },\n { path: ['opa.bundleMode'], value: true, label: 'opa.bundleMode' },\n { path: ['opa.formatter'], value: 'opa-fmt-rego-v1', label: 'opa.formatter' },\n ],\n updates,\n warnings,\n );\n\n const associations = current['files.associations'];\n const manifestAssociation = buildPolicyManifestAssociationPattern(\n repositoryRoot,\n workspaceDirectory,\n );\n if (associations === undefined) {\n updates.push({\n path: ['files.associations'],\n value: { [manifestAssociation]: 'json' },\n });\n } else if (\n associations !== null &&\n typeof associations === 'object' &&\n !Array.isArray(associations)\n ) {\n collectSafeScalarUpdates(\n current,\n [\n {\n path: ['files.associations', manifestAssociation],\n value: 'json',\n label: `files.associations.\"${manifestAssociation}\"`,\n },\n ],\n updates,\n warnings,\n );\n } else {\n warnings.push('VS Code setting \"files.associations\" is customized and was left unchanged.');\n }\n\n if (bundles && bundles.length > 0) {\n const desiredJsonSchemas = bundles.map((bundle) =>\n buildBundleInputJsonSchema(repositoryRoot, workspaceDirectory, bundle),\n );\n const jsonSchemas = current['json.schemas'];\n if (jsonSchemas === undefined) {\n updates.push({ path: ['json.schemas'], value: desiredJsonSchemas });\n } else if (Array.isArray(jsonSchemas)) {\n let merged = [...jsonSchemas];\n desiredJsonSchemas.forEach((desired) => {\n const hasExactMatch = merged.some(\n (entry) =>\n entry !== null &&\n typeof entry === 'object' &&\n !Array.isArray(entry) &&\n configurationValuesEqual(entry, desired),\n );\n if (hasExactMatch) {\n return;\n }\n const sameUrlIndex = merged.findIndex(\n (entry) =>\n entry !== null &&\n typeof entry === 'object' &&\n !Array.isArray(entry) &&\n typeof (entry as Record<string, unknown>).url === 'string' &&\n (entry as Record<string, unknown>).url === desired.url,\n );\n if (sameUrlIndex === -1) {\n merged = [...merged, desired];\n return;\n }\n const existing = merged[sameUrlIndex] as Record<string, unknown>;\n const existingMatches = existing.fileMatch;\n if (\n !Array.isArray(existingMatches) ||\n !existingMatches.every((m) => typeof m === 'string')\n ) {\n warnings.push(\n `VS Code setting \"json.schemas\" has a repository-specific entry for the ${desired.url} schema and was left unchanged.`,\n );\n return;\n }\n const unioned = mergeStringArray(existingMatches, desired.fileMatch);\n if (!configurationValuesEqual(unioned, existingMatches)) {\n merged = merged.map((entry, index) =>\n index === sameUrlIndex ? { ...existing, fileMatch: unioned } : entry,\n );\n }\n });\n if (!configurationValuesEqual(merged, jsonSchemas)) {\n updates.push({ path: ['json.schemas'], value: merged });\n }\n } else {\n warnings.push('VS Code setting \"json.schemas\" is customized and was left unchanged.');\n }\n }\n\n const regoSettings = current['[rego]'];\n if (regoSettings === undefined) {\n updates.push({\n path: ['[rego]'],\n value: {\n 'editor.defaultFormatter': POLICY_VSCODE_EXTENSION,\n 'editor.formatOnSave': true,\n 'editor.insertSpaces': false,\n 'editor.tabSize': 4,\n },\n });\n } else if (\n regoSettings !== null &&\n typeof regoSettings === 'object' &&\n !Array.isArray(regoSettings)\n ) {\n collectSafeScalarUpdates(\n current,\n [\n {\n path: ['[rego]', 'editor.defaultFormatter'],\n value: POLICY_VSCODE_EXTENSION,\n label: '[rego].editor.defaultFormatter',\n },\n {\n path: ['[rego]', 'editor.formatOnSave'],\n value: true,\n label: '[rego].editor.formatOnSave',\n },\n {\n path: ['[rego]', 'editor.insertSpaces'],\n value: false,\n label: '[rego].editor.insertSpaces',\n },\n {\n path: ['[rego]', 'editor.tabSize'],\n value: 4,\n label: '[rego].editor.tabSize',\n },\n ],\n updates,\n warnings,\n );\n } else {\n warnings.push('VS Code language setting \"[rego]\" is customized and was left unchanged.');\n }\n\n return applyEditorUpdates(contents, updates, 'VS Code policy settings', warnings);\n}\n\n/**\n * Merge OPA extension recommendations without disturbing unknown entries.\n *\n * VS Code's `extensions.json` schema supports `unwantedRecommendations`, so\n * the syntax-only extension can be removed from recommendations and discouraged.\n *\n * @param contents - Existing `.vscode/extensions.json` JSONC\n * @returns Merged extension recommendations and warnings\n */\nexport function mergePolicyEditorExtensions(contents: string | null): PolicyEditorMergeResult {\n const parsed = parseEditorObject(contents, 'VS Code policy extension recommendations');\n if ('warning' in parsed) {\n return { contents: parsed.contents, warnings: [parsed.warning] };\n }\n const { current } = parsed;\n const updates: JsoncUpdate[] = [];\n const warnings: string[] = [];\n\n const recommendations = current.recommendations;\n if (\n recommendations === undefined ||\n (Array.isArray(recommendations) && recommendations.every((value) => typeof value === 'string'))\n ) {\n const existing = recommendations ?? [];\n const desired = mergeStringArray(existing, [POLICY_VSCODE_EXTENSION]).filter(\n (value) => value !== CONFLICTING_POLICY_VSCODE_EXTENSION,\n );\n if (!configurationValuesEqual(existing, desired)) {\n updates.push({ path: ['recommendations'], value: desired });\n }\n } else {\n warnings.push(\n 'VS Code extension recommendations use an unknown shape and were left unchanged.',\n );\n }\n\n const unwanted = current.unwantedRecommendations;\n if (\n unwanted === undefined ||\n (Array.isArray(unwanted) && unwanted.every((value) => typeof value === 'string'))\n ) {\n const existing = unwanted ?? [];\n const desired = mergeStringArray(existing, [CONFLICTING_POLICY_VSCODE_EXTENSION]);\n if (!configurationValuesEqual(existing, desired)) {\n updates.push({ path: ['unwantedRecommendations'], value: desired });\n }\n } else {\n warnings.push(\n 'VS Code unwanted extension recommendations use an unknown shape and were left unchanged.',\n );\n }\n\n return applyEditorUpdates(\n contents,\n updates,\n 'VS Code policy extension recommendations',\n warnings,\n );\n}\n\n/**\n * Build a VS Code lint task for one bundle.\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundle - Package root and local directory basename\n * @returns VS Code task object\n */\nexport function buildPolicyLintTask(\n repositoryRoot: string,\n workspaceDirectory: string,\n bundle: PolicyBundleRef = policyBundleRef(POLICY_STARTER_ROOT),\n): Record<string, unknown> {\n const workspace = repositoryRelativePosix(repositoryRoot, workspaceDirectory);\n const path = workspace === '.' ? bundle.bundleDir : `${workspace}/${bundle.bundleDir}`;\n return {\n label: `policy: lint ${bundle.root}`,\n type: 'process',\n command: 'transcend',\n args: ['policy', 'lint', path, '--noInteractive'],\n group: {\n kind: 'test',\n isDefault: false,\n },\n problemMatcher: [],\n };\n}\n\n/**\n * Build the aggregate \"policy: lint\" task (runs all bundles).\n *\n * @param bundles - Local publish units\n * @returns VS Code task object\n */\nexport function buildPolicyAggregateLintTask(\n bundles: readonly PolicyBundleRef[],\n): Record<string, unknown> {\n return {\n label: 'policy: lint',\n dependsOn: bundles.map((bundle) => `policy: lint ${bundle.root}`),\n dependsOrder: 'sequence',\n group: {\n kind: 'test',\n isDefault: true,\n },\n problemMatcher: [],\n };\n}\n\n/**\n * Merge policy lint tasks while preserving custom tasks.\n *\n * When `bundles` is provided, creates per-bundle tasks and an aggregate.\n * Managed `policy: lint*` tasks are updated in place as roots grow. When\n * omitted, skips task creation (init without bundles).\n *\n * @param contents - Existing `.vscode/tasks.json` JSONC\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundles - Local publish units to generate tasks for\n * @returns Merged task configuration and warnings\n */\nexport function mergePolicyEditorTasks(\n contents: string | null,\n repositoryRoot: string,\n workspaceDirectory: string,\n bundles?: readonly PolicyBundleRef[],\n): PolicyEditorMergeResult {\n const parsed = parseEditorObject(contents, 'VS Code policy tasks');\n if ('warning' in parsed) {\n return { contents: parsed.contents, warnings: [parsed.warning] };\n }\n const { current } = parsed;\n const updates: JsoncUpdate[] = [];\n const warnings: string[] = [];\n const version = current.version;\n if (version === undefined) {\n updates.push({ path: ['version'], value: '2.0.0' });\n } else if (version !== '2.0.0') {\n warnings.push('VS Code task schema version is customized and was left unchanged.');\n }\n\n if (!bundles || bundles.length === 0) {\n return applyEditorUpdates(contents, updates, 'VS Code policy tasks', warnings);\n }\n\n const desiredPerBundleTasks = bundles.map((bundle) =>\n buildPolicyLintTask(repositoryRoot, workspaceDirectory, bundle),\n );\n const desiredAggregateTask = buildPolicyAggregateLintTask(bundles);\n const desiredTasks = [...desiredPerBundleTasks, desiredAggregateTask];\n const managedLabels = new Set(desiredTasks.map((task) => task.label as string));\n const tasks = current.tasks;\n if (tasks === undefined) {\n updates.push({ path: ['tasks'], value: desiredTasks });\n } else if (Array.isArray(tasks)) {\n let merged = [...tasks];\n let changed = false;\n desiredTasks.forEach((desired) => {\n const label = desired.label as string;\n const matchingIndex = merged.findIndex(\n (task) =>\n task !== null &&\n typeof task === 'object' &&\n !Array.isArray(task) &&\n (task as Record<string, unknown>).label === label,\n );\n if (matchingIndex === -1) {\n merged = [...merged, desired];\n changed = true;\n return;\n }\n if (!configurationValuesEqual(merged[matchingIndex], desired)) {\n if (managedLabels.has(label)) {\n merged = merged.map((task, index) => (index === matchingIndex ? desired : task));\n changed = true;\n } else {\n warnings.push(\n `VS Code task \"${label}\" has repository-specific customization and was left unchanged.`,\n );\n }\n }\n });\n if (changed) {\n updates.push({ path: ['tasks'], value: merged });\n }\n } else {\n warnings.push('VS Code tasks use an unknown shape and were left unchanged.');\n }\n\n return applyEditorUpdates(contents, updates, 'VS Code policy tasks', warnings);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"policy-scaffold-config-DtRELUZx.mjs","names":[],"sources":["../src/lib/policy/policy-scaffold-artifacts.ts","../src/lib/policy/policy-scaffold-config.ts"],"sourcesContent":["import { posix } from 'node:path';\n\nimport {\n POLICY_STARTER_BUNDLE_DIRECTORY,\n POLICY_STARTER_OPA_VERSION,\n} from './policy-scaffold-templates.js';\n\n/** Regal version pinned by generated policy validation. */\nexport const POLICY_STARTER_REGAL_VERSION = '0.42.0';\n\n/** Repository-relative generated workflow path. */\nexport const POLICY_CI_WORKFLOW_PATH = '.github/workflows/transcend-policy.yml';\n\n/** Immutable actions/checkout v6 release commit. */\nexport const ACTIONS_CHECKOUT_SHA = 'd23441a48e516b6c34aea4fa41551a30e30af803';\n\n/** Immutable open-policy-agent/setup-opa v2.4.0 release commit. */\nexport const SETUP_OPA_SHA = 'b2b258e089860efaadaaf71bf6e3aecb4a3eeff1';\n\n/** Immutable open-policy-agent/setup-regal v2.0.0 release commit. */\nexport const SETUP_REGAL_SHA = '761188c3b435761fa254beca508a44875619648f';\n\n/**\n * Prefix a path below the repository-relative policy workspace.\n *\n * @param workspaceDirectory - POSIX repository-relative workspace\n * @param child - POSIX workspace-relative path or pattern\n * @returns Repository-relative path or pattern\n */\nfunction workspacePath(workspaceDirectory: string, child: string): string {\n return workspaceDirectory === '.' ? child : posix.join(workspaceDirectory, child);\n}\n\n/**\n * Generate credential-free, validation-only Policy Engine CI.\n *\n * Path filters watch the whole policy workspace. Lint runs against each\n * publishable `{root}-bundle/` directory.\n *\n * @param options - Repository paths and exact CLI release\n * @returns Complete GitHub Actions workflow YAML\n */\nexport function generatePolicyGithubActionsWorkflow(options: {\n /** Exact released Transcend CLI version. */\n cliVersion: string;\n /** Policy workspace directory relative to the repository root. */\n workspaceDirectory: string;\n /**\n * Publish directories to lint, relative to the repository root.\n *\n * Pass an empty array for a workspace with no bundles yet (`policy init`).\n * Defaults to the disposable starter bundle under the workspace when omitted.\n */\n bundleDirectories?: readonly string[];\n}): string {\n const bundleDirectories = options.bundleDirectories ?? [\n workspacePath(options.workspaceDirectory, POLICY_STARTER_BUNDLE_DIRECTORY),\n ];\n const watchedPaths = [\n workspacePath(options.workspaceDirectory, '**/*.rego'),\n workspacePath(options.workspaceDirectory, '**/*.json'),\n workspacePath(options.workspaceDirectory, '**/*.yaml'),\n workspacePath(options.workspaceDirectory, '**/*.yml'),\n workspacePath(options.workspaceDirectory, '**/.manifest'),\n workspacePath(options.workspaceDirectory, '.regal/config.yaml'),\n workspacePath(options.workspaceDirectory, '.regal.yaml'),\n POLICY_CI_WORKFLOW_PATH,\n ];\n const pathFilters = watchedPaths.map((path) => ` - ${JSON.stringify(path)}`).join('\\n');\n\n let lintJob: string;\n if (bundleDirectories.length === 0) {\n lintJob = ` lint:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@${ACTIONS_CHECKOUT_SHA} # v6\n - name: No publishable bundles yet\n run: >-\n echo \"Run transcend policy new to add a bundle;\n policy new refreshes this workflow automatically.\"\n`;\n } else if (bundleDirectories.length === 1) {\n lintJob = ` lint:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@${ACTIONS_CHECKOUT_SHA} # v6\n - name: Set up OPA\n uses: open-policy-agent/setup-opa@${SETUP_OPA_SHA} # v2.4.0\n with:\n version: ${JSON.stringify(POLICY_STARTER_OPA_VERSION)}\n - name: Set up Regal\n uses: open-policy-agent/setup-regal@${SETUP_REGAL_SHA} # v2.0.0\n with:\n version: ${JSON.stringify(POLICY_STARTER_REGAL_VERSION)}\n - name: Install the Transcend CLI\n run: npm install --global @transcend-io/cli@${options.cliVersion}\n - name: Validate Policy Engine project\n env:\n POLICY_DIRECTORY: ${JSON.stringify(bundleDirectories[0])}\n run: >-\n transcend policy lint\n \"$POLICY_DIRECTORY\"\n --noInteractive\n --json\n`;\n } else {\n lintJob = ` lint:\n runs-on: ubuntu-latest\n strategy:\n fail-fast: false\n matrix:\n policy_directory:\n${bundleDirectories.map((directory) => ` - ${JSON.stringify(directory)}`).join('\\n')}\n steps:\n - uses: actions/checkout@${ACTIONS_CHECKOUT_SHA} # v6\n - name: Set up OPA\n uses: open-policy-agent/setup-opa@${SETUP_OPA_SHA} # v2.4.0\n with:\n version: ${JSON.stringify(POLICY_STARTER_OPA_VERSION)}\n - name: Set up Regal\n uses: open-policy-agent/setup-regal@${SETUP_REGAL_SHA} # v2.0.0\n with:\n version: ${JSON.stringify(POLICY_STARTER_REGAL_VERSION)}\n - name: Install the Transcend CLI\n run: npm install --global @transcend-io/cli@${options.cliVersion}\n - name: Validate Policy Engine project\n env:\n POLICY_DIRECTORY: \\${{ matrix.policy_directory }}\n run: transcend policy lint \"$POLICY_DIRECTORY\" --noInteractive --json\n`;\n }\n\n return `# Generated by transcend policy init.\nname: Transcend Policy\n\non:\n pull_request:\n paths:\n${pathFilters}\n push:\n paths:\n${pathFilters}\n\npermissions:\n contents: read\n\njobs:\n${lintJob}`;\n}\n","import { relative, sep } from 'node:path';\n\nimport {\n mergeJsonc,\n mergeStringArray,\n parseJsoncObject,\n type JsoncUpdate,\n} from '../scaffolding/jsonc.js';\nimport {\n buildBundleDirectoryName,\n PERMISSIONS_POLICY_INPUT_SCHEMA_ID,\n POLICY_INPUT_SCHEMA_FILENAME,\n POLICY_MANIFEST_FILENAME,\n POLICY_STARTER_ROOT,\n PolicyTemplate,\n type PolicyTemplateName,\n} from './policy-scaffold-templates.js';\n\n/** Recommended VS Code extension for OPA and Regal authoring. */\nexport const POLICY_VSCODE_EXTENSION = 'tsandall.opa';\n\n/** Conflicting syntax-only VS Code extension. */\nexport const CONFLICTING_POLICY_VSCODE_EXTENSION = 'glebbash.opa-highlight-only';\n\n/**\n * Local publish unit: Rego package root plus its workspace-relative folder basename.\n *\n * `bundleDir` is not required to be `{root}-bundle` — `policy new --bundle-dir` may override it.\n */\nexport interface PolicyBundleRef {\n /** Package-path root (`.manifest` / Regal `project.roots`). */\n root: string;\n /** Basename of the publish directory under the policy workspace. */\n bundleDir: string;\n /**\n * Scaffold template that produced this bundle, when known.\n *\n * Permissions bundles point VS Code `json.schemas` at the published input\n * schema `$id` so editor validation tracks the live contract.\n */\n template?: PolicyTemplateName;\n}\n\n/**\n * Build a bundle ref with the default `{root}-bundle` directory.\n *\n * @param root - Package root\n * @param template - Optional scaffold template\n * @returns Bundle ref\n */\nexport function policyBundleRef(root: string, template?: PolicyTemplateName): PolicyBundleRef {\n return template === undefined\n ? { root, bundleDir: buildBundleDirectoryName(root) }\n : { root, bundleDir: buildBundleDirectoryName(root), template };\n}\n\n/** Result of safely merging one repository editor artifact. */\nexport interface PolicyEditorMergeResult {\n /** Complete merged contents, or the unchanged original when unsafe. */\n contents: string;\n /** Actionable preservation warnings. */\n warnings: string[];\n}\n\n/** One desired scalar setting that must not replace a customization. */\ninterface DesiredSetting {\n /** JSONC object path. */\n path: string[];\n /** Desired generated value. */\n value: unknown;\n /** Human-readable setting key. */\n label: string;\n}\n\n/**\n * Compare JSON-compatible configuration values.\n *\n * @param left - Existing value\n * @param right - Desired value\n * @returns Whether the values are structurally equal\n */\nfunction configurationValuesEqual(left: unknown, right: unknown): boolean {\n return JSON.stringify(left) === JSON.stringify(right);\n}\n\n/**\n * Parse an editor object while preserving malformed existing content.\n *\n * @param contents - Existing JSONC, or null\n * @param label - Configuration label\n * @returns Parsed object or a preservation result\n */\nfunction parseEditorObject(\n contents: string | null,\n label: string,\n):\n | {\n /** Parsed JSONC object. */\n current: Record<string, unknown>;\n }\n | {\n /** Complete unchanged contents. */\n contents: string;\n /** Actionable warning. */\n warning: string;\n } {\n try {\n return { current: parseJsoncObject(contents ?? '{}\\n', label) };\n } catch (error) {\n return {\n contents: contents ?? '{}\\n',\n warning: `${error instanceof Error ? error.message : String(error)} Existing content was left unchanged.`,\n };\n }\n}\n\n/**\n * Apply safe JSONC updates or preserve the existing file when comments prevent it.\n *\n * @param contents - Existing JSONC\n * @param updates - Proven-safe updates\n * @param label - Configuration label\n * @param warnings - Existing merge warnings\n * @returns Complete contents and warnings\n */\nfunction applyEditorUpdates(\n contents: string | null,\n updates: readonly JsoncUpdate[],\n label: string,\n warnings: string[],\n): PolicyEditorMergeResult {\n if (updates.length === 0) {\n return { contents: contents ?? '{}\\n', warnings };\n }\n try {\n return {\n contents: mergeJsonc(contents, updates, label),\n warnings,\n };\n } catch (error) {\n return {\n contents: contents ?? '{}\\n',\n warnings: [\n ...warnings,\n `${error instanceof Error ? error.message : String(error)} Existing content was left unchanged.`,\n ],\n };\n }\n}\n\n/**\n * Add absent scalar settings without replacing repository customizations.\n *\n * @param current - Parsed editor object\n * @param desired - Desired settings\n * @param updates - Safe updates to append to\n * @param warnings - Conflict warnings to append to\n */\nfunction collectSafeScalarUpdates(\n current: Record<string, unknown>,\n desired: readonly DesiredSetting[],\n updates: JsoncUpdate[],\n warnings: string[],\n): void {\n desired.forEach(({ path, value, label }) => {\n const existing = path.reduce<unknown>((parent, key) => {\n return parent && typeof parent === 'object' && !Array.isArray(parent)\n ? (parent as Record<string, unknown>)[key]\n : undefined;\n }, current);\n if (existing === undefined) {\n updates.push({ path, value });\n } else if (!configurationValuesEqual(existing, value)) {\n warnings.push(\n `VS Code setting \"${label}\" has a repository-specific value and was left unchanged.`,\n );\n }\n });\n}\n\n/**\n * Build a `${workspaceFolder}/…` path for a repository-relative POSIX path.\n *\n * @param repositoryRelativePath - Path relative to the repository root\n * @returns VS Code workspace path\n */\nfunction buildWorkspaceFolderPath(repositoryRelativePath: string): string {\n return repositoryRelativePath === '.'\n ? '${workspaceFolder}'\n : `\\${workspaceFolder}/${repositoryRelativePath}`;\n}\n\n/**\n * Repository-relative POSIX path from the root that owns `.vscode`.\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param absolutePath - Absolute path below the repository\n * @returns POSIX repository-relative path\n */\nfunction repositoryRelativePosix(repositoryRoot: string, absolutePath: string): string {\n return relative(repositoryRoot, absolutePath).split(sep).join('/') || '.';\n}\n\n/**\n * Build the workspace-relative OPA publish root for a bundle.\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundle - Package root and local directory basename\n * @returns VS Code workspace path\n */\nfunction buildOpaBundleRoot(\n repositoryRoot: string,\n workspaceDirectory: string,\n bundle: PolicyBundleRef = policyBundleRef(POLICY_STARTER_ROOT),\n): string {\n const workspace = repositoryRelativePosix(repositoryRoot, workspaceDirectory);\n const path = workspace === '.' ? bundle.bundleDir : `${workspace}/${bundle.bundleDir}`;\n return buildWorkspaceFolderPath(path);\n}\n\n/**\n * Build a `files.associations` glob for every `.manifest` under the workspace.\n *\n * Patterns that contain `/` are matched against the absolute file path, so a\n * leading `**\\/` prefix is required for a portable workspace-relative association.\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @returns Association pattern key\n */\nfunction buildPolicyManifestAssociationPattern(\n repositoryRoot: string,\n workspaceDirectory: string,\n): string {\n const workspace = repositoryRelativePosix(repositoryRoot, workspaceDirectory);\n return workspace === '.'\n ? `**/${POLICY_MANIFEST_FILENAME}`\n : `**/${workspace}/**/${POLICY_MANIFEST_FILENAME}`;\n}\n\n/**\n * Build `json.schemas` fileMatch globs for a bundle's input documents.\n *\n * Permissions templates use the published schema `$id` so VS Code validation\n * tracks the live contract. Generic bundles use the local `input.schema.json`\n * (OPA / offline tooling still always use the local file via `--schema`).\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundle - Package root and local directory basename\n * @returns Absolute-style fileMatch paths and schema URL\n */\nfunction buildBundleInputJsonSchema(\n repositoryRoot: string,\n workspaceDirectory: string,\n bundle: PolicyBundleRef = policyBundleRef(POLICY_STARTER_ROOT),\n): {\n /** VS Code fileMatch globs (leading slash = workspace-relative). */\n fileMatch: string[];\n /** Schema URL (local path or published Permissions `$id`). */\n url: string;\n} {\n const workspace = repositoryRelativePosix(repositoryRoot, workspaceDirectory);\n const bundlePrefix =\n workspace === '.' ? `/${bundle.bundleDir}` : `/${workspace}/${bundle.bundleDir}`;\n const schemaUrl =\n bundle.template === PolicyTemplate.Permissions\n ? PERMISSIONS_POLICY_INPUT_SCHEMA_ID\n : workspace === '.'\n ? `./${bundle.bundleDir}/${POLICY_INPUT_SCHEMA_FILENAME}`\n : `./${workspace}/${bundle.bundleDir}/${POLICY_INPUT_SCHEMA_FILENAME}`;\n return {\n fileMatch: [`${bundlePrefix}/input.json`, `${bundlePrefix}/input.example.json`],\n url: schemaUrl,\n };\n}\n\n/**\n * Merge strict workspace-scoped OPA and Rego editor settings.\n *\n * Setting names match the authoritative `open-policy-agent/vscode-opa`\n * extension metadata. Existing conflicting values are preserved and reported.\n *\n * When `bundles` is provided, `opa.roots` and `json.schemas` are populated\n * for those bundles. When omitted (init without bundles), `opa.roots` and\n * `json.schemas` are left empty or unset.\n *\n * @param contents - Existing `.vscode/settings.json` JSONC\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundles - Local publish units to add to `opa.roots` and `json.schemas`\n * @returns Merged settings and conflict warnings\n */\nexport function mergePolicyEditorSettings(\n contents: string | null,\n repositoryRoot: string,\n workspaceDirectory: string,\n bundles?: readonly PolicyBundleRef[],\n): PolicyEditorMergeResult {\n const parsed = parseEditorObject(contents, 'VS Code policy settings');\n if ('warning' in parsed) {\n return { contents: parsed.contents, warnings: [parsed.warning] };\n }\n const { current } = parsed;\n const updates: JsoncUpdate[] = [];\n const warnings: string[] = [];\n\n if (bundles && bundles.length > 0) {\n const roots = current['opa.roots'];\n const desiredRoots = bundles.map((bundle) =>\n buildOpaBundleRoot(repositoryRoot, workspaceDirectory, bundle),\n );\n if (roots === undefined) {\n updates.push({ path: ['opa.roots'], value: desiredRoots });\n } else if (Array.isArray(roots) && roots.every((value) => typeof value === 'string')) {\n const merged = mergeStringArray(roots, desiredRoots);\n if (!configurationValuesEqual(merged, roots)) {\n updates.push({ path: ['opa.roots'], value: merged });\n }\n } else {\n warnings.push('VS Code setting \"opa.roots\" is customized and was left unchanged.');\n }\n }\n\n collectSafeScalarUpdates(\n current,\n [\n { path: ['opa.checkOnSave'], value: true, label: 'opa.checkOnSave' },\n { path: ['opa.strictMode'], value: true, label: 'opa.strictMode' },\n { path: ['opa.bundleMode'], value: true, label: 'opa.bundleMode' },\n { path: ['opa.formatter'], value: 'opa-fmt-rego-v1', label: 'opa.formatter' },\n ],\n updates,\n warnings,\n );\n\n const associations = current['files.associations'];\n const manifestAssociation = buildPolicyManifestAssociationPattern(\n repositoryRoot,\n workspaceDirectory,\n );\n if (associations === undefined) {\n updates.push({\n path: ['files.associations'],\n value: { [manifestAssociation]: 'json' },\n });\n } else if (\n associations !== null &&\n typeof associations === 'object' &&\n !Array.isArray(associations)\n ) {\n collectSafeScalarUpdates(\n current,\n [\n {\n path: ['files.associations', manifestAssociation],\n value: 'json',\n label: `files.associations.\"${manifestAssociation}\"`,\n },\n ],\n updates,\n warnings,\n );\n } else {\n warnings.push('VS Code setting \"files.associations\" is customized and was left unchanged.');\n }\n\n if (bundles && bundles.length > 0) {\n const desiredJsonSchemas = bundles.map((bundle) =>\n buildBundleInputJsonSchema(repositoryRoot, workspaceDirectory, bundle),\n );\n const jsonSchemas = current['json.schemas'];\n if (jsonSchemas === undefined) {\n updates.push({ path: ['json.schemas'], value: desiredJsonSchemas });\n } else if (Array.isArray(jsonSchemas)) {\n let merged = [...jsonSchemas];\n desiredJsonSchemas.forEach((desired) => {\n const hasExactMatch = merged.some(\n (entry) =>\n entry !== null &&\n typeof entry === 'object' &&\n !Array.isArray(entry) &&\n configurationValuesEqual(entry, desired),\n );\n if (hasExactMatch) {\n return;\n }\n const sameUrlIndex = merged.findIndex(\n (entry) =>\n entry !== null &&\n typeof entry === 'object' &&\n !Array.isArray(entry) &&\n typeof (entry as Record<string, unknown>).url === 'string' &&\n (entry as Record<string, unknown>).url === desired.url,\n );\n if (sameUrlIndex === -1) {\n merged = [...merged, desired];\n return;\n }\n const existing = merged[sameUrlIndex] as Record<string, unknown>;\n const existingMatches = existing.fileMatch;\n if (\n !Array.isArray(existingMatches) ||\n !existingMatches.every((m) => typeof m === 'string')\n ) {\n warnings.push(\n `VS Code setting \"json.schemas\" has a repository-specific entry for the ${desired.url} schema and was left unchanged.`,\n );\n return;\n }\n const unioned = mergeStringArray(existingMatches, desired.fileMatch);\n if (!configurationValuesEqual(unioned, existingMatches)) {\n merged = merged.map((entry, index) =>\n index === sameUrlIndex ? { ...existing, fileMatch: unioned } : entry,\n );\n }\n });\n if (!configurationValuesEqual(merged, jsonSchemas)) {\n updates.push({ path: ['json.schemas'], value: merged });\n }\n } else {\n warnings.push('VS Code setting \"json.schemas\" is customized and was left unchanged.');\n }\n }\n\n const regoSettings = current['[rego]'];\n if (regoSettings === undefined) {\n updates.push({\n path: ['[rego]'],\n value: {\n 'editor.defaultFormatter': POLICY_VSCODE_EXTENSION,\n 'editor.formatOnSave': true,\n 'editor.insertSpaces': false,\n 'editor.tabSize': 4,\n },\n });\n } else if (\n regoSettings !== null &&\n typeof regoSettings === 'object' &&\n !Array.isArray(regoSettings)\n ) {\n collectSafeScalarUpdates(\n current,\n [\n {\n path: ['[rego]', 'editor.defaultFormatter'],\n value: POLICY_VSCODE_EXTENSION,\n label: '[rego].editor.defaultFormatter',\n },\n {\n path: ['[rego]', 'editor.formatOnSave'],\n value: true,\n label: '[rego].editor.formatOnSave',\n },\n {\n path: ['[rego]', 'editor.insertSpaces'],\n value: false,\n label: '[rego].editor.insertSpaces',\n },\n {\n path: ['[rego]', 'editor.tabSize'],\n value: 4,\n label: '[rego].editor.tabSize',\n },\n ],\n updates,\n warnings,\n );\n } else {\n warnings.push('VS Code language setting \"[rego]\" is customized and was left unchanged.');\n }\n\n return applyEditorUpdates(contents, updates, 'VS Code policy settings', warnings);\n}\n\n/**\n * Merge OPA extension recommendations without disturbing unknown entries.\n *\n * VS Code's `extensions.json` schema supports `unwantedRecommendations`, so\n * the syntax-only extension can be removed from recommendations and discouraged.\n *\n * @param contents - Existing `.vscode/extensions.json` JSONC\n * @returns Merged extension recommendations and warnings\n */\nexport function mergePolicyEditorExtensions(contents: string | null): PolicyEditorMergeResult {\n const parsed = parseEditorObject(contents, 'VS Code policy extension recommendations');\n if ('warning' in parsed) {\n return { contents: parsed.contents, warnings: [parsed.warning] };\n }\n const { current } = parsed;\n const updates: JsoncUpdate[] = [];\n const warnings: string[] = [];\n\n const recommendations = current.recommendations;\n if (\n recommendations === undefined ||\n (Array.isArray(recommendations) && recommendations.every((value) => typeof value === 'string'))\n ) {\n const existing = recommendations ?? [];\n const desired = mergeStringArray(existing, [POLICY_VSCODE_EXTENSION]).filter(\n (value) => value !== CONFLICTING_POLICY_VSCODE_EXTENSION,\n );\n if (!configurationValuesEqual(existing, desired)) {\n updates.push({ path: ['recommendations'], value: desired });\n }\n } else {\n warnings.push(\n 'VS Code extension recommendations use an unknown shape and were left unchanged.',\n );\n }\n\n const unwanted = current.unwantedRecommendations;\n if (\n unwanted === undefined ||\n (Array.isArray(unwanted) && unwanted.every((value) => typeof value === 'string'))\n ) {\n const existing = unwanted ?? [];\n const desired = mergeStringArray(existing, [CONFLICTING_POLICY_VSCODE_EXTENSION]);\n if (!configurationValuesEqual(existing, desired)) {\n updates.push({ path: ['unwantedRecommendations'], value: desired });\n }\n } else {\n warnings.push(\n 'VS Code unwanted extension recommendations use an unknown shape and were left unchanged.',\n );\n }\n\n return applyEditorUpdates(\n contents,\n updates,\n 'VS Code policy extension recommendations',\n warnings,\n );\n}\n\n/**\n * Build a VS Code lint task for one bundle.\n *\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundle - Package root and local directory basename\n * @returns VS Code task object\n */\nexport function buildPolicyLintTask(\n repositoryRoot: string,\n workspaceDirectory: string,\n bundle: PolicyBundleRef = policyBundleRef(POLICY_STARTER_ROOT),\n): Record<string, unknown> {\n const workspace = repositoryRelativePosix(repositoryRoot, workspaceDirectory);\n const path = workspace === '.' ? bundle.bundleDir : `${workspace}/${bundle.bundleDir}`;\n return {\n label: `policy: lint ${bundle.root}`,\n type: 'process',\n command: 'transcend',\n args: ['policy', 'lint', path, '--noInteractive'],\n group: {\n kind: 'test',\n isDefault: false,\n },\n problemMatcher: [],\n };\n}\n\n/**\n * Build the aggregate \"policy: lint\" task (runs all bundles).\n *\n * @param bundles - Local publish units\n * @returns VS Code task object\n */\nexport function buildPolicyAggregateLintTask(\n bundles: readonly PolicyBundleRef[],\n): Record<string, unknown> {\n return {\n label: 'policy: lint',\n dependsOn: bundles.map((bundle) => `policy: lint ${bundle.root}`),\n dependsOrder: 'sequence',\n group: {\n kind: 'test',\n isDefault: true,\n },\n problemMatcher: [],\n };\n}\n\n/**\n * Merge policy lint tasks while preserving custom tasks.\n *\n * When `bundles` is provided, creates per-bundle tasks and an aggregate.\n * Managed `policy: lint*` tasks are updated in place as roots grow. When\n * omitted, skips task creation (init without bundles).\n *\n * @param contents - Existing `.vscode/tasks.json` JSONC\n * @param repositoryRoot - Root that owns `.vscode`\n * @param workspaceDirectory - Selected policy workspace directory\n * @param bundles - Local publish units to generate tasks for\n * @returns Merged task configuration and warnings\n */\nexport function mergePolicyEditorTasks(\n contents: string | null,\n repositoryRoot: string,\n workspaceDirectory: string,\n bundles?: readonly PolicyBundleRef[],\n): PolicyEditorMergeResult {\n const parsed = parseEditorObject(contents, 'VS Code policy tasks');\n if ('warning' in parsed) {\n return { contents: parsed.contents, warnings: [parsed.warning] };\n }\n const { current } = parsed;\n const updates: JsoncUpdate[] = [];\n const warnings: string[] = [];\n const version = current.version;\n if (version === undefined) {\n updates.push({ path: ['version'], value: '2.0.0' });\n } else if (version !== '2.0.0') {\n warnings.push('VS Code task schema version is customized and was left unchanged.');\n }\n\n if (!bundles || bundles.length === 0) {\n return applyEditorUpdates(contents, updates, 'VS Code policy tasks', warnings);\n }\n\n const desiredPerBundleTasks = bundles.map((bundle) =>\n buildPolicyLintTask(repositoryRoot, workspaceDirectory, bundle),\n );\n const desiredAggregateTask = buildPolicyAggregateLintTask(bundles);\n const desiredTasks = [...desiredPerBundleTasks, desiredAggregateTask];\n const managedLabels = new Set(desiredTasks.map((task) => task.label as string));\n const tasks = current.tasks;\n if (tasks === undefined) {\n updates.push({ path: ['tasks'], value: desiredTasks });\n } else if (Array.isArray(tasks)) {\n let merged = [...tasks];\n let changed = false;\n desiredTasks.forEach((desired) => {\n const label = desired.label as string;\n const matchingIndex = merged.findIndex(\n (task) =>\n task !== null &&\n typeof task === 'object' &&\n !Array.isArray(task) &&\n (task as Record<string, unknown>).label === label,\n );\n if (matchingIndex === -1) {\n merged = [...merged, desired];\n changed = true;\n return;\n }\n if (!configurationValuesEqual(merged[matchingIndex], desired)) {\n if (managedLabels.has(label)) {\n merged = merged.map((task, index) => (index === matchingIndex ? desired : task));\n changed = true;\n } else {\n warnings.push(\n `VS Code task \"${label}\" has repository-specific customization and was left unchanged.`,\n );\n }\n }\n });\n if (changed) {\n updates.push({ path: ['tasks'], value: merged });\n }\n } else {\n warnings.push('VS Code tasks use an unknown shape and were left unchanged.');\n }\n\n return applyEditorUpdates(contents, updates, 'VS Code policy tasks', warnings);\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{a as e}from"./constants-BlKpAm3R.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import n from"colors";import{mapSeries as r}from"@transcend-io/utils";import{decodeCodec as i}from"@transcend-io/type-utils";import*as a from"io-ts";import{REDUCED_REQUESTS_FOR_DATA_SILO_COUNT as o,buildTranscendGraphQLClient as s,createSombraGotInstance as c,makeGraphQLRequest as l,withTransientRetry as u}from"@transcend-io/sdk";import d from"cli-progress";async function f(e,{dataSiloId:n}){let{listReducedRequestsForDataSilo:{totalCount:r}}=await l(e,o,{variables:{input:{dataSiloId:n,isResolved:!1}},logger:t});return r}const p=a.type({id:a.string,name:a.string,partition:a.string}),m=a.intersection([a.type({identifier:a.string,type:a.string,coreIdentifier:a.string,dataSiloId:a.string,requestId:a.string,nonce:a.string,requestCreatedAt:a.string,daysUntilOverdue:a.number,attributes:a.array(a.type({key:a.string,values:a.array(a.string)}))}),a.partial({partition:a.union([p,a.null])})]);async function h(e,{dataSiloId:n,limit:r=100,offset:o=0,requestType:s}){try{let c=await u(`pullCronPageOfIdentifiers`,()=>e.get(`v1/data-silo/${n}/pending-requests/${s}`,{searchParams:{offset:o,limit:r}}).json(),{logger:t,maxAttempts:6,baseDelayMs:500}),{items:l}=i(a.type({items:a.array(m)}),c);return l}catch(e){throw Error(`Received an error from server: ${e?.response?.body||e?.message}`)}}async function g({dataSiloId:i,auth:a,sombraAuth:o,actions:l,apiPageSize:u=100,savePageSize:p=1e3,onSave:m,transcendUrl:g=e,skipRequestCount:_=!1}){if(p%u!==0)throw Error(`savePageSize must be a multiple of apiPageSize. savePageSize: ${p}, apiPageSize: ${u}`);let v=await c(g,a,{logger:t,sombraApiKey:o,sombraUrl:process.env.SOMBRA_URL}),y=s(g,a),b=0;_||(b=await f(y,{dataSiloId:i})),t.info(n.magenta(`Pulling ${_?`all`:b} outstanding request identifiers for data silo: "${i}" for requests of types "${l.join(`", "`)}"`));let x=new Date().getTime(),S=new d.SingleBar({},d.Presets.shades_classic),C=new Set,w=[],T=[];_||S.start(b,0),await r(l,async e=>{let r=0,a=!0;for(;a;){let o=await h(v,{dataSiloId:i,limit:u,offset:r,requestType:e}),s=o.map(t=>(C.add(t.requestId),{...t,action:e})),c=s.map(({attributes:e,partition:t,...n})=>({...n,...t?{partitionId:t.id,partitionName:t.name,partitionKey:t.partition}:{},...e.reduce((e,t)=>Object.assign(e,{[t.key]:t.values.join(`,`)}),{})}));w.push(...s),T.push(...c),T.length>=p&&(await m(T),T=[]),a=o.length===u,r+=u,_?t.info(n.magenta(`Pulled ${o.length} outstanding identifiers for ${C.size} requests`)):S.update(C.size)}}),T.length>0&&await m(T),_||S.stop();let E=new Date().getTime()-x;return t.info(n.green(`Successfully pulled ${w.length} outstanding identifiers from ${C.size} requests in "${E/1e3}" seconds!`)),{identifiers:w}}export{f as a,h as i,m as n,p as r,g as t};
2
- //# sourceMappingURL=pullChunkedCustomSiloOutstandingIdentifiers-BfNNWqeI.mjs.map
1
+ import{a as e}from"./constants-BCmVqlrM.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import n from"colors";import{mapSeries as r}from"@transcend-io/utils";import{decodeCodec as i}from"@transcend-io/type-utils";import*as a from"io-ts";import{REDUCED_REQUESTS_FOR_DATA_SILO_COUNT as o,buildTranscendGraphQLClient as s,createSombraGotInstance as c,makeGraphQLRequest as l,withTransientRetry as u}from"@transcend-io/sdk";import d from"cli-progress";async function f(e,{dataSiloId:n}){let{listReducedRequestsForDataSilo:{totalCount:r}}=await l(e,o,{variables:{input:{dataSiloId:n,isResolved:!1}},logger:t});return r}const p=a.type({id:a.string,name:a.string,partition:a.string}),m=a.intersection([a.type({identifier:a.string,type:a.string,coreIdentifier:a.string,dataSiloId:a.string,requestId:a.string,nonce:a.string,requestCreatedAt:a.string,daysUntilOverdue:a.number,attributes:a.array(a.type({key:a.string,values:a.array(a.string)}))}),a.partial({partition:a.union([p,a.null])})]);async function h(e,{dataSiloId:n,limit:r=100,offset:o=0,requestType:s}){try{let c=await u(`pullCronPageOfIdentifiers`,()=>e.get(`v1/data-silo/${n}/pending-requests/${s}`,{searchParams:{offset:o,limit:r}}).json(),{logger:t,maxAttempts:6,baseDelayMs:500}),{items:l}=i(a.type({items:a.array(m)}),c);return l}catch(e){throw Error(`Received an error from server: ${e?.response?.body||e?.message}`)}}async function g({dataSiloId:i,auth:a,sombraAuth:o,actions:l,apiPageSize:u=100,savePageSize:p=1e3,onSave:m,transcendUrl:g=e,skipRequestCount:_=!1}){if(p%u!==0)throw Error(`savePageSize must be a multiple of apiPageSize. savePageSize: ${p}, apiPageSize: ${u}`);let v=await c(g,a,{logger:t,sombraApiKey:o,sombraUrl:process.env.SOMBRA_URL}),y=s(g,a),b=0;_||(b=await f(y,{dataSiloId:i})),t.info(n.magenta(`Pulling ${_?`all`:b} outstanding request identifiers for data silo: "${i}" for requests of types "${l.join(`", "`)}"`));let x=new Date().getTime(),S=new d.SingleBar({},d.Presets.shades_classic),C=new Set,w=[],T=[];_||S.start(b,0),await r(l,async e=>{let r=0,a=!0;for(;a;){let o=await h(v,{dataSiloId:i,limit:u,offset:r,requestType:e}),s=o.map(t=>(C.add(t.requestId),{...t,action:e})),c=s.map(({attributes:e,partition:t,...n})=>({...n,...t?{partitionId:t.id,partitionName:t.name,partitionKey:t.partition}:{},...e.reduce((e,t)=>Object.assign(e,{[t.key]:t.values.join(`,`)}),{})}));w.push(...s),T.push(...c),T.length>=p&&(await m(T),T=[]),a=o.length===u,r+=u,_?t.info(n.magenta(`Pulled ${o.length} outstanding identifiers for ${C.size} requests`)):S.update(C.size)}}),T.length>0&&await m(T),_||S.stop();let E=new Date().getTime()-x;return t.info(n.green(`Successfully pulled ${w.length} outstanding identifiers from ${C.size} requests in "${E/1e3}" seconds!`)),{identifiers:w}}export{f as a,h as i,m as n,p as r,g as t};
2
+ //# sourceMappingURL=pullChunkedCustomSiloOutstandingIdentifiers-BaHp-jxs.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"pullChunkedCustomSiloOutstandingIdentifiers-BfNNWqeI.mjs","names":[],"sources":["../src/lib/graphql/fetchRequestDataSiloActiveCount.ts","../src/lib/cron/pullCronPageOfIdentifiers.ts","../src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts"],"sourcesContent":["import { makeGraphQLRequest, REDUCED_REQUESTS_FOR_DATA_SILO_COUNT } from '@transcend-io/sdk';\nimport { GraphQLClient } from 'graphql-request';\n\nimport { logger } from '../../logger.js';\n\n/**\n * Get number of open requests for a data silo\n *\n * @param client - GraphQL client\n * @param options - Filter options\n * @returns List of request identifiers\n */\nexport async function fetchRequestDataSiloActiveCount(\n client: GraphQLClient,\n {\n dataSiloId,\n }: {\n /** Data silo ID */\n dataSiloId: string;\n },\n): Promise<number> {\n const {\n listReducedRequestsForDataSilo: { totalCount },\n } = await makeGraphQLRequest<{\n /** Requests */\n listReducedRequestsForDataSilo: {\n /** Total count */\n totalCount: number;\n };\n }>(client, REDUCED_REQUESTS_FOR_DATA_SILO_COUNT, {\n variables: {\n input: {\n dataSiloId,\n isResolved: false,\n },\n },\n logger,\n });\n\n return totalCount;\n}\n","import { RequestAction } from '@transcend-io/privacy-types';\nimport { withTransientRetry } from '@transcend-io/sdk';\nimport { decodeCodec } from '@transcend-io/type-utils';\nimport type { Got } from 'got';\nimport * as t from 'io-ts';\n\nimport { logger } from '../../logger.js';\n\n/** Consent partition metadata when a DSR is scoped to a partition */\nexport const CronPartition = t.type({\n /** ID of the partition */\n id: t.string,\n /** The human readable name of the partition */\n name: t.string,\n /** The unique identifying partition value */\n partition: t.string,\n});\n\n/** Type override */\nexport type CronPartition = t.TypeOf<typeof CronPartition>;\n\nexport const CronIdentifier = t.intersection([\n t.type({\n /** The identifier value */\n identifier: t.string,\n /** The type of identifier */\n type: t.string,\n /** The core identifier of the request */\n coreIdentifier: t.string,\n /** The ID of the underlying data silo */\n dataSiloId: t.string,\n /** The ID of the underlying request */\n requestId: t.string,\n /** The request nonce */\n nonce: t.string,\n /** The time the request was created */\n requestCreatedAt: t.string,\n /** The number of days until the request is overdue */\n daysUntilOverdue: t.number,\n /** Request attributes */\n attributes: t.array(\n t.type({\n key: t.string,\n values: t.array(t.string),\n }),\n ),\n }),\n t.partial({\n /** The consent partition that scopes this request, when applicable */\n partition: t.union([CronPartition, t.null]),\n }),\n]);\n\n/** Type override */\nexport type CronIdentifier = t.TypeOf<typeof CronIdentifier>;\n\n/**\n * Pull a offset of identifiers for a cron job\n *\n * @see https://docs.transcend.io/docs/api-reference/GET/v1/data-silo/(id)/pending-requests/(type)\n * @param sombra - Sombra instance configured to make requests\n * @param options - Additional options\n * @returns Successfully submitted request\n */\nexport async function pullCronPageOfIdentifiers(\n sombra: Got,\n {\n dataSiloId,\n limit = 100,\n offset = 0,\n requestType,\n }: {\n /** Data Silo ID */\n dataSiloId: string;\n /** Type of request */\n requestType: RequestAction;\n /** Number of identifiers to pull in */\n limit?: number;\n /** Page to pull in */\n offset?: number;\n },\n): Promise<CronIdentifier[]> {\n try {\n // `GET pending-requests` is a read and therefore safe to retry on transient\n // gateway / network errors. Customers running `transcend cron\n // pull-identifiers` against large silos would otherwise see the command\n // abort on a single 502 from the Sombra reverse tunnel.\n const response = await withTransientRetry(\n 'pullCronPageOfIdentifiers',\n () =>\n sombra\n .get(`v1/data-silo/${dataSiloId}/pending-requests/${requestType}`, {\n searchParams: {\n offset,\n limit,\n },\n })\n .json(),\n { logger, maxAttempts: 6, baseDelayMs: 500 },\n );\n\n const { items } = decodeCodec(\n t.type({\n items: t.array(CronIdentifier),\n }),\n response,\n );\n return items;\n } catch (err) {\n throw new Error(`Received an error from server: ${err?.response?.body || err?.message}`);\n }\n}\n","import { RequestAction } from '@transcend-io/privacy-types';\nimport { buildTranscendGraphQLClient, createSombraGotInstance } from '@transcend-io/sdk';\nimport { mapSeries } 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 { fetchRequestDataSiloActiveCount } from '../graphql/index.js';\nimport { pullCronPageOfIdentifiers, CronIdentifier } from './pullCronPageOfIdentifiers.js';\n\n/**\n * A CSV formatted identifier\n */\nexport type CsvFormattedIdentifier = {\n [k in string]: string | null | boolean | number;\n};\n\nexport interface CronIdentifierWithAction extends CronIdentifier {\n /** The request action that the identifier relates to */\n action: RequestAction;\n}\n\n/**\n * Pull the set of identifiers outstanding for a cron or AVC integration\n *\n * This function is designed to be used in a loop, and will call the onSave callback\n * with a chunk of identifiers when the savePageSize is reached.\n *\n * @param options - Options\n * @returns The identifiers and identifiers formatted for CSV\n */\nexport async function pullChunkedCustomSiloOutstandingIdentifiers({\n dataSiloId,\n auth,\n sombraAuth,\n actions,\n apiPageSize = 100,\n savePageSize = 1000,\n onSave,\n transcendUrl = DEFAULT_TRANSCEND_API,\n skipRequestCount = false,\n}: {\n /** Transcend API key authentication */\n auth: string;\n /** Data Silo ID to pull down jobs for */\n dataSiloId: string;\n /** The request actions to fetch */\n actions: RequestAction[];\n /** How many identifiers to pull in a single call to the backend */\n apiPageSize: number;\n /** How many identifiers to save at a time (usually to a CSV file, should be a multiple of apiPageSize) */\n savePageSize: number;\n /** Callback function called when a chunk of identifiers is ready to be saved */\n onSave: (chunk: CsvFormattedIdentifier[]) => Promise<void>;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Sombra API key authentication */\n sombraAuth?: string;\n /** Skip request count */\n skipRequestCount?: boolean;\n}): Promise<{\n /** Raw Identifiers */\n identifiers: CronIdentifierWithAction[];\n}> {\n // Validate savePageSize\n if (savePageSize % apiPageSize !== 0) {\n throw new Error(\n `savePageSize must be a multiple of apiPageSize. savePageSize: ${savePageSize}, apiPageSize: ${apiPageSize}`,\n );\n }\n\n // Create sombra instance to communicate with\n const sombra = await createSombraGotInstance(transcendUrl, auth, {\n logger,\n sombraApiKey: sombraAuth,\n sombraUrl: process.env.SOMBRA_URL,\n });\n\n // Create GraphQL client to connect to Transcend backend\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n let totalRequestCount = 0;\n if (!skipRequestCount) {\n totalRequestCount = await fetchRequestDataSiloActiveCount(client, {\n dataSiloId,\n });\n }\n\n logger.info(\n colors.magenta(\n `Pulling ${skipRequestCount ? 'all' : totalRequestCount} outstanding request identifiers ` +\n `for data silo: \"${dataSiloId}\" for requests of types \"${actions.join('\", \"')}\"`,\n ),\n );\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 const foundRequestIds = new Set<string>();\n\n // identifiers found in total\n const identifiers: CronIdentifierWithAction[] = [];\n // current chunk of identifiers to be saved\n let currentChunk: CsvFormattedIdentifier[] = [];\n\n // map over each action\n if (!skipRequestCount) {\n progressBar.start(totalRequestCount, 0);\n }\n await mapSeries(actions, async (action) => {\n let offset = 0;\n let shouldContinue = true;\n\n // Fetch a page of identifiers\n while (shouldContinue) {\n const pageIdentifiers = await pullCronPageOfIdentifiers(sombra, {\n dataSiloId,\n limit: apiPageSize,\n offset,\n requestType: action,\n });\n\n const identifiersWithAction: CronIdentifierWithAction[] = pageIdentifiers.map(\n (identifier) => {\n foundRequestIds.add(identifier.requestId);\n return {\n ...identifier,\n action,\n };\n },\n );\n\n const csvFormattedIdentifiers = identifiersWithAction.map(\n ({ attributes, partition, ...identifier }) => ({\n ...identifier,\n ...(partition\n ? {\n partitionId: partition.id,\n partitionName: partition.name,\n partitionKey: partition.partition,\n }\n : {}),\n ...attributes.reduce(\n (acc, val) =>\n Object.assign(acc, {\n [val.key]: val.values.join(','),\n }),\n {},\n ),\n }),\n );\n\n identifiers.push(...identifiersWithAction);\n currentChunk.push(...csvFormattedIdentifiers);\n\n // Check if we've reached the savePageSize and call the onSave callback\n if (currentChunk.length >= savePageSize) {\n await onSave(currentChunk);\n currentChunk = [];\n }\n\n shouldContinue = pageIdentifiers.length === apiPageSize;\n offset += apiPageSize;\n if (!skipRequestCount) {\n progressBar.update(foundRequestIds.size);\n } else {\n logger.info(\n colors.magenta(\n `Pulled ${pageIdentifiers.length} outstanding identifiers for ${foundRequestIds.size} requests`,\n ),\n );\n }\n }\n });\n\n // Save any remaining identifiers in the current chunk\n if (currentChunk.length > 0) {\n await onSave(currentChunk);\n }\n\n if (!skipRequestCount) {\n progressBar.stop();\n }\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(\n colors.green(\n `Successfully pulled ${identifiers.length} outstanding identifiers from ${\n foundRequestIds.size\n } requests in \"${totalTime / 1000}\" seconds!`,\n ),\n );\n\n return { identifiers };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pullChunkedCustomSiloOutstandingIdentifiers-BaHp-jxs.mjs","names":[],"sources":["../src/lib/graphql/fetchRequestDataSiloActiveCount.ts","../src/lib/cron/pullCronPageOfIdentifiers.ts","../src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts"],"sourcesContent":["import { makeGraphQLRequest, REDUCED_REQUESTS_FOR_DATA_SILO_COUNT } from '@transcend-io/sdk';\nimport { GraphQLClient } from 'graphql-request';\n\nimport { logger } from '../../logger.js';\n\n/**\n * Get number of open requests for a data silo\n *\n * @param client - GraphQL client\n * @param options - Filter options\n * @returns List of request identifiers\n */\nexport async function fetchRequestDataSiloActiveCount(\n client: GraphQLClient,\n {\n dataSiloId,\n }: {\n /** Data silo ID */\n dataSiloId: string;\n },\n): Promise<number> {\n const {\n listReducedRequestsForDataSilo: { totalCount },\n } = await makeGraphQLRequest<{\n /** Requests */\n listReducedRequestsForDataSilo: {\n /** Total count */\n totalCount: number;\n };\n }>(client, REDUCED_REQUESTS_FOR_DATA_SILO_COUNT, {\n variables: {\n input: {\n dataSiloId,\n isResolved: false,\n },\n },\n logger,\n });\n\n return totalCount;\n}\n","import { RequestAction } from '@transcend-io/privacy-types';\nimport { withTransientRetry } from '@transcend-io/sdk';\nimport { decodeCodec } from '@transcend-io/type-utils';\nimport type { Got } from 'got';\nimport * as t from 'io-ts';\n\nimport { logger } from '../../logger.js';\n\n/** Consent partition metadata when a DSR is scoped to a partition */\nexport const CronPartition = t.type({\n /** ID of the partition */\n id: t.string,\n /** The human readable name of the partition */\n name: t.string,\n /** The unique identifying partition value */\n partition: t.string,\n});\n\n/** Type override */\nexport type CronPartition = t.TypeOf<typeof CronPartition>;\n\nexport const CronIdentifier = t.intersection([\n t.type({\n /** The identifier value */\n identifier: t.string,\n /** The type of identifier */\n type: t.string,\n /** The core identifier of the request */\n coreIdentifier: t.string,\n /** The ID of the underlying data silo */\n dataSiloId: t.string,\n /** The ID of the underlying request */\n requestId: t.string,\n /** The request nonce */\n nonce: t.string,\n /** The time the request was created */\n requestCreatedAt: t.string,\n /** The number of days until the request is overdue */\n daysUntilOverdue: t.number,\n /** Request attributes */\n attributes: t.array(\n t.type({\n key: t.string,\n values: t.array(t.string),\n }),\n ),\n }),\n t.partial({\n /** The consent partition that scopes this request, when applicable */\n partition: t.union([CronPartition, t.null]),\n }),\n]);\n\n/** Type override */\nexport type CronIdentifier = t.TypeOf<typeof CronIdentifier>;\n\n/**\n * Pull a offset of identifiers for a cron job\n *\n * @see https://docs.transcend.io/docs/api-reference/GET/v1/data-silo/(id)/pending-requests/(type)\n * @param sombra - Sombra instance configured to make requests\n * @param options - Additional options\n * @returns Successfully submitted request\n */\nexport async function pullCronPageOfIdentifiers(\n sombra: Got,\n {\n dataSiloId,\n limit = 100,\n offset = 0,\n requestType,\n }: {\n /** Data Silo ID */\n dataSiloId: string;\n /** Type of request */\n requestType: RequestAction;\n /** Number of identifiers to pull in */\n limit?: number;\n /** Page to pull in */\n offset?: number;\n },\n): Promise<CronIdentifier[]> {\n try {\n // `GET pending-requests` is a read and therefore safe to retry on transient\n // gateway / network errors. Customers running `transcend cron\n // pull-identifiers` against large silos would otherwise see the command\n // abort on a single 502 from the Sombra reverse tunnel.\n const response = await withTransientRetry(\n 'pullCronPageOfIdentifiers',\n () =>\n sombra\n .get(`v1/data-silo/${dataSiloId}/pending-requests/${requestType}`, {\n searchParams: {\n offset,\n limit,\n },\n })\n .json(),\n { logger, maxAttempts: 6, baseDelayMs: 500 },\n );\n\n const { items } = decodeCodec(\n t.type({\n items: t.array(CronIdentifier),\n }),\n response,\n );\n return items;\n } catch (err) {\n throw new Error(`Received an error from server: ${err?.response?.body || err?.message}`);\n }\n}\n","import { RequestAction } from '@transcend-io/privacy-types';\nimport { buildTranscendGraphQLClient, createSombraGotInstance } from '@transcend-io/sdk';\nimport { mapSeries } 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 { fetchRequestDataSiloActiveCount } from '../graphql/index.js';\nimport { pullCronPageOfIdentifiers, CronIdentifier } from './pullCronPageOfIdentifiers.js';\n\n/**\n * A CSV formatted identifier\n */\nexport type CsvFormattedIdentifier = {\n [k in string]: string | null | boolean | number;\n};\n\nexport interface CronIdentifierWithAction extends CronIdentifier {\n /** The request action that the identifier relates to */\n action: RequestAction;\n}\n\n/**\n * Pull the set of identifiers outstanding for a cron or AVC integration\n *\n * This function is designed to be used in a loop, and will call the onSave callback\n * with a chunk of identifiers when the savePageSize is reached.\n *\n * @param options - Options\n * @returns The identifiers and identifiers formatted for CSV\n */\nexport async function pullChunkedCustomSiloOutstandingIdentifiers({\n dataSiloId,\n auth,\n sombraAuth,\n actions,\n apiPageSize = 100,\n savePageSize = 1000,\n onSave,\n transcendUrl = DEFAULT_TRANSCEND_API,\n skipRequestCount = false,\n}: {\n /** Transcend API key authentication */\n auth: string;\n /** Data Silo ID to pull down jobs for */\n dataSiloId: string;\n /** The request actions to fetch */\n actions: RequestAction[];\n /** How many identifiers to pull in a single call to the backend */\n apiPageSize: number;\n /** How many identifiers to save at a time (usually to a CSV file, should be a multiple of apiPageSize) */\n savePageSize: number;\n /** Callback function called when a chunk of identifiers is ready to be saved */\n onSave: (chunk: CsvFormattedIdentifier[]) => Promise<void>;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Sombra API key authentication */\n sombraAuth?: string;\n /** Skip request count */\n skipRequestCount?: boolean;\n}): Promise<{\n /** Raw Identifiers */\n identifiers: CronIdentifierWithAction[];\n}> {\n // Validate savePageSize\n if (savePageSize % apiPageSize !== 0) {\n throw new Error(\n `savePageSize must be a multiple of apiPageSize. savePageSize: ${savePageSize}, apiPageSize: ${apiPageSize}`,\n );\n }\n\n // Create sombra instance to communicate with\n const sombra = await createSombraGotInstance(transcendUrl, auth, {\n logger,\n sombraApiKey: sombraAuth,\n sombraUrl: process.env.SOMBRA_URL,\n });\n\n // Create GraphQL client to connect to Transcend backend\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n let totalRequestCount = 0;\n if (!skipRequestCount) {\n totalRequestCount = await fetchRequestDataSiloActiveCount(client, {\n dataSiloId,\n });\n }\n\n logger.info(\n colors.magenta(\n `Pulling ${skipRequestCount ? 'all' : totalRequestCount} outstanding request identifiers ` +\n `for data silo: \"${dataSiloId}\" for requests of types \"${actions.join('\", \"')}\"`,\n ),\n );\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 const foundRequestIds = new Set<string>();\n\n // identifiers found in total\n const identifiers: CronIdentifierWithAction[] = [];\n // current chunk of identifiers to be saved\n let currentChunk: CsvFormattedIdentifier[] = [];\n\n // map over each action\n if (!skipRequestCount) {\n progressBar.start(totalRequestCount, 0);\n }\n await mapSeries(actions, async (action) => {\n let offset = 0;\n let shouldContinue = true;\n\n // Fetch a page of identifiers\n while (shouldContinue) {\n const pageIdentifiers = await pullCronPageOfIdentifiers(sombra, {\n dataSiloId,\n limit: apiPageSize,\n offset,\n requestType: action,\n });\n\n const identifiersWithAction: CronIdentifierWithAction[] = pageIdentifiers.map(\n (identifier) => {\n foundRequestIds.add(identifier.requestId);\n return {\n ...identifier,\n action,\n };\n },\n );\n\n const csvFormattedIdentifiers = identifiersWithAction.map(\n ({ attributes, partition, ...identifier }) => ({\n ...identifier,\n ...(partition\n ? {\n partitionId: partition.id,\n partitionName: partition.name,\n partitionKey: partition.partition,\n }\n : {}),\n ...attributes.reduce(\n (acc, val) =>\n Object.assign(acc, {\n [val.key]: val.values.join(','),\n }),\n {},\n ),\n }),\n );\n\n identifiers.push(...identifiersWithAction);\n currentChunk.push(...csvFormattedIdentifiers);\n\n // Check if we've reached the savePageSize and call the onSave callback\n if (currentChunk.length >= savePageSize) {\n await onSave(currentChunk);\n currentChunk = [];\n }\n\n shouldContinue = pageIdentifiers.length === apiPageSize;\n offset += apiPageSize;\n if (!skipRequestCount) {\n progressBar.update(foundRequestIds.size);\n } else {\n logger.info(\n colors.magenta(\n `Pulled ${pageIdentifiers.length} outstanding identifiers for ${foundRequestIds.size} requests`,\n ),\n );\n }\n }\n });\n\n // Save any remaining identifiers in the current chunk\n if (currentChunk.length > 0) {\n await onSave(currentChunk);\n }\n\n if (!skipRequestCount) {\n progressBar.stop();\n }\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(\n colors.green(\n `Successfully pulled ${identifiers.length} outstanding identifiers from ${\n foundRequestIds.size\n } requests in \"${totalTime / 1000}\" seconds!`,\n ),\n );\n\n return { identifiers };\n}\n"],"mappings":"0bAYA,eAAsB,EACpB,EACA,CACE,cAKe,CACjB,GAAM,CACJ,+BAAgC,CAAE,eAChC,MAAM,EAMP,EAAQ,EAAsC,CAC/C,UAAW,CACT,MAAO,CACL,aACA,WAAY,GACb,CACF,CACD,SACD,CAAC,CAEF,OAAO,EC9BT,MAAa,EAAgB,EAAE,KAAK,CAElC,GAAI,EAAE,OAEN,KAAM,EAAE,OAER,UAAW,EAAE,OACd,CAAC,CAKW,EAAiB,EAAE,aAAa,CAC3C,EAAE,KAAK,CAEL,WAAY,EAAE,OAEd,KAAM,EAAE,OAER,eAAgB,EAAE,OAElB,WAAY,EAAE,OAEd,UAAW,EAAE,OAEb,MAAO,EAAE,OAET,iBAAkB,EAAE,OAEpB,iBAAkB,EAAE,OAEpB,WAAY,EAAE,MACZ,EAAE,KAAK,CACL,IAAK,EAAE,OACP,OAAQ,EAAE,MAAM,EAAE,OAAO,CAC1B,CAAC,CACH,CACF,CAAC,CACF,EAAE,QAAQ,CAER,UAAW,EAAE,MAAM,CAAC,EAAe,EAAE,KAAK,CAAC,CAC5C,CAAC,CACH,CAAC,CAaF,eAAsB,EACpB,EACA,CACE,aACA,QAAQ,IACR,SAAS,EACT,eAWyB,CAC3B,GAAI,CAKF,IAAM,EAAW,MAAM,EACrB,gCAEE,EACG,IAAI,gBAAgB,EAAW,oBAAoB,IAAe,CACjE,aAAc,CACZ,SACA,QACD,CACF,CAAC,CACD,MAAM,CACX,CAAE,SAAQ,YAAa,EAAG,YAAa,IAAK,CAC7C,CAEK,CAAE,SAAU,EAChB,EAAE,KAAK,CACL,MAAO,EAAE,MAAM,EAAe,CAC/B,CAAC,CACF,EACD,CACD,OAAO,QACA,EAAK,CACZ,MAAU,MAAM,kCAAkC,GAAK,UAAU,MAAQ,GAAK,UAAU,EC7E5F,eAAsB,EAA4C,CAChE,aACA,OACA,aACA,UACA,cAAc,IACd,eAAe,IACf,SACA,eAAe,EACf,mBAAmB,IAuBlB,CAED,GAAI,EAAe,IAAgB,EACjC,MAAU,MACR,iEAAiE,EAAa,iBAAiB,IAChG,CAIH,IAAM,EAAS,MAAM,EAAwB,EAAc,EAAM,CAC/D,SACA,aAAc,EACd,UAAW,QAAQ,IAAI,WACxB,CAAC,CAGI,EAAS,EAA4B,EAAc,EAAK,CAE1D,EAAoB,EACnB,IACH,EAAoB,MAAM,EAAgC,EAAQ,CAChE,aACD,CAAC,EAGJ,EAAO,KACL,EAAO,QACL,WAAW,EAAmB,MAAQ,EAAkB,mDACnC,EAAW,2BAA2B,EAAQ,KAAK,OAAO,CAAC,GACjF,CACF,CAGD,IAAM,EAAK,IAAI,MAAM,CAAC,SAAS,CAEzB,EAAc,IAAI,EAAY,UAAU,EAAE,CAAE,EAAY,QAAQ,eAAe,CAC/E,EAAkB,IAAI,IAGtB,EAA0C,EAAE,CAE9C,EAAyC,EAAE,CAG1C,GACH,EAAY,MAAM,EAAmB,EAAE,CAEzC,MAAM,EAAU,EAAS,KAAO,IAAW,CACzC,IAAI,EAAS,EACT,EAAiB,GAGrB,KAAO,GAAgB,CACrB,IAAM,EAAkB,MAAM,EAA0B,EAAQ,CAC9D,aACA,MAAO,EACP,SACA,YAAa,EACd,CAAC,CAEI,EAAoD,EAAgB,IACvE,IACC,EAAgB,IAAI,EAAW,UAAU,CAClC,CACL,GAAG,EACH,SACD,EAEJ,CAEK,EAA0B,EAAsB,KACnD,CAAE,aAAY,YAAW,GAAG,MAAkB,CAC7C,GAAG,EACH,GAAI,EACA,CACE,YAAa,EAAU,GACvB,cAAe,EAAU,KACzB,aAAc,EAAU,UACzB,CACD,EAAE,CACN,GAAG,EAAW,QACX,EAAK,IACJ,OAAO,OAAO,EAAK,EAChB,EAAI,KAAM,EAAI,OAAO,KAAK,IAAI,CAChC,CAAC,CACJ,EAAE,CACH,CACF,EACF,CAED,EAAY,KAAK,GAAG,EAAsB,CAC1C,EAAa,KAAK,GAAG,EAAwB,CAGzC,EAAa,QAAU,IACzB,MAAM,EAAO,EAAa,CAC1B,EAAe,EAAE,EAGnB,EAAiB,EAAgB,SAAW,EAC5C,GAAU,EACL,EAGH,EAAO,KACL,EAAO,QACL,UAAU,EAAgB,OAAO,+BAA+B,EAAgB,KAAK,WACtF,CACF,CAND,EAAY,OAAO,EAAgB,KAAK,GAS5C,CAGE,EAAa,OAAS,GACxB,MAAM,EAAO,EAAa,CAGvB,GACH,EAAY,MAAM,CAGpB,IAAM,EADK,IAAI,MAAM,CAAC,SACF,CAAG,EAUvB,OARA,EAAO,KACL,EAAO,MACL,uBAAuB,EAAY,OAAO,gCACxC,EAAgB,KACjB,gBAAgB,EAAY,IAAK,YACnC,CACF,CAEM,CAAE,cAAa"}
@@ -1,2 +1,2 @@
1
- import{a as e}from"./constants-BlKpAm3R.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{r as n}from"./fetchAllRequests-BI0tFl_a.mjs";import{a as r}from"./writeCsv-CNZrq3zU.mjs";import{RequestStatus as i}from"@transcend-io/privacy-types";import{groupBy as a,uniq as o}from"lodash-es";import s from"colors";import{map as c}from"@transcend-io/utils";import{buildTranscendGraphQLClient as l,createSombraGotInstance as u,fetchAllRequestEnrichers as d,fetchAllRequestIdentifiers as f,validateSombraVersion as p}from"@transcend-io/sdk";async function m({file:m,auth:h,sombraAuth:g,requestActions:_=[],concurrency:v=100,transcendUrl:y=e}){let b=l(y,h),x=await u(y,h,{logger:t,sombraApiKey:g,sombraUrl:process.env.SOMBRA_URL});t.info(s.magenta(`Pulling manual enrichment requests, filtered for actions: ${_.join(`,`)}`));let S=await n(b,{actions:_,statuses:[i.Enriching]});await p(b,{logger:t});let C=[];await c(S,async e=>{let n=await d(b,{filterBy:{requestId:e.id},logger:t});if(n.filter(({status:e})=>e===`ACTION_REQUIRED`)){let r=await f(b,x,{filterBy:{requestId:e.id},skipSombraCheck:!0,logger:t});C.push({...e,requestIdentifiers:r,requestEnrichers:n})}},{concurrency:v});let w=C.map(({attributeValues:e,requestIdentifiers:t,requestEnrichers:n,...r})=>({...r,...Object.entries(a(t,`name`)).reduce((e,[t,n])=>Object.assign(e,{[t]:n.map(({value:e})=>e).join(`,`)}),{}),...Object.entries(a(e,`attributeKey.name`)).reduce((e,[t,n])=>Object.assign(e,{[t]:n.map(({name:e})=>e).join(`,`)}),{})}));return await r(m,w,o(w.map(e=>Object.keys(e)).flat())),t.info(s.green(`Successfully wrote ${C.length} requests to file "${m}"`)),C}export{m as t};
2
- //# sourceMappingURL=pullManualEnrichmentIdentifiersToCsv-B_lWa6Yg.mjs.map
1
+ import{a as e}from"./constants-BCmVqlrM.mjs";import{t}from"./logger-CPu6zbyQ.mjs";import{r as n}from"./fetchAllRequests-BI0tFl_a.mjs";import{a as r}from"./writeCsv-CNZrq3zU.mjs";import{RequestStatus as i}from"@transcend-io/privacy-types";import{groupBy as a,uniq as o}from"lodash-es";import s from"colors";import{map as c}from"@transcend-io/utils";import{buildTranscendGraphQLClient as l,createSombraGotInstance as u,fetchAllRequestEnrichers as d,fetchAllRequestIdentifiers as f,validateSombraVersion as p}from"@transcend-io/sdk";async function m({file:m,auth:h,sombraAuth:g,requestActions:_=[],concurrency:v=100,transcendUrl:y=e}){let b=l(y,h),x=await u(y,h,{logger:t,sombraApiKey:g,sombraUrl:process.env.SOMBRA_URL});t.info(s.magenta(`Pulling manual enrichment requests, filtered for actions: ${_.join(`,`)}`));let S=await n(b,{actions:_,statuses:[i.Enriching]});await p(b,{logger:t});let C=[];await c(S,async e=>{let n=await d(b,{filterBy:{requestId:e.id},logger:t});if(n.filter(({status:e})=>e===`ACTION_REQUIRED`)){let r=await f(b,x,{filterBy:{requestId:e.id},skipSombraCheck:!0,logger:t});C.push({...e,requestIdentifiers:r,requestEnrichers:n})}},{concurrency:v});let w=C.map(({attributeValues:e,requestIdentifiers:t,requestEnrichers:n,...r})=>({...r,...Object.entries(a(t,`name`)).reduce((e,[t,n])=>Object.assign(e,{[t]:n.map(({value:e})=>e).join(`,`)}),{}),...Object.entries(a(e,`attributeKey.name`)).reduce((e,[t,n])=>Object.assign(e,{[t]:n.map(({name:e})=>e).join(`,`)}),{})}));return await r(m,w,o(w.map(e=>Object.keys(e)).flat())),t.info(s.green(`Successfully wrote ${C.length} requests to file "${m}"`)),C}export{m as t};
2
+ //# sourceMappingURL=pullManualEnrichmentIdentifiersToCsv-CiEFIvl_.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"pullManualEnrichmentIdentifiersToCsv-B_lWa6Yg.mjs","names":[],"sources":["../src/lib/manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts"],"sourcesContent":["import { RequestAction, RequestStatus } from '@transcend-io/privacy-types';\nimport {\n buildTranscendGraphQLClient,\n createSombraGotInstance,\n fetchAllRequestEnrichers,\n fetchAllRequestIdentifiers,\n validateSombraVersion,\n type RequestEnricher,\n type RequestIdentifier,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport colors from 'colors';\nimport { groupBy, uniq } from 'lodash-es';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { type PrivacyRequest, fetchAllRequests } from '../graphql/index.js';\nimport { writeCsv } from '../helpers/writeCsv.js';\n\nexport interface PrivacyRequestWithIdentifiers extends PrivacyRequest {\n /** Request Enrichers */\n requestEnrichers: RequestEnricher[];\n /** Request Identifiers */\n requestIdentifiers: RequestIdentifier[];\n}\n\n/**\n * Pull the set of manual enrichment jobs to CSV\n *\n * @param options - Options\n * @returns List of requests with identifiers\n */\nexport async function pullManualEnrichmentIdentifiersToCsv({\n file,\n auth,\n sombraAuth,\n requestActions = [],\n concurrency = 100,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** CSV file path */\n file: string;\n /** Transcend API key authentication */\n auth: string;\n /** Sombra API key */\n sombraAuth?: string;\n /** Concurrency */\n concurrency?: number;\n /** The request actions to fetch */\n requestActions?: RequestAction[];\n /** API URL for Transcend backend */\n transcendUrl?: string;\n}): Promise<PrivacyRequestWithIdentifiers[]> {\n // Find all requests made before createdAt that are in a removing data state\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n const sombra = await createSombraGotInstance(transcendUrl, auth, {\n logger,\n sombraApiKey: sombraAuth,\n sombraUrl: process.env.SOMBRA_URL,\n });\n\n logger.info(\n colors.magenta(\n `Pulling manual enrichment requests, filtered for actions: ${requestActions.join(',')}`,\n ),\n );\n\n // Pull all privacy requests\n const allRequests = await fetchAllRequests(client, {\n actions: requestActions,\n statuses: [RequestStatus.Enriching],\n });\n\n await validateSombraVersion(client, { logger });\n\n // Requests to save\n const savedRequests: PrivacyRequestWithIdentifiers[] = [];\n\n // Filter down requests to what is needed\n await map(\n allRequests,\n async (request) => {\n // Fetch enrichers\n const requestEnrichers = await fetchAllRequestEnrichers(client, {\n filterBy: { requestId: request.id },\n logger,\n });\n\n // Check if manual enrichment exists for that request\n const hasManualEnrichment = requestEnrichers.filter(\n ({ status }) => status === 'ACTION_REQUIRED',\n );\n\n // Save request to queue\n if (hasManualEnrichment) {\n const requestIdentifiers = await fetchAllRequestIdentifiers(client, sombra, {\n filterBy: { requestId: request.id },\n skipSombraCheck: true,\n logger,\n });\n savedRequests.push({\n ...request,\n requestIdentifiers,\n requestEnrichers,\n });\n }\n },\n {\n concurrency,\n },\n );\n\n const data = savedRequests.map(\n ({\n attributeValues,\n requestIdentifiers,\n requestEnrichers, // eslint-disable-line @typescript-eslint/no-unused-vars\n ...request\n }) => ({\n ...request,\n // flatten identifiers\n ...Object.entries(groupBy(requestIdentifiers, 'name')).reduce(\n (acc, [key, values]) =>\n Object.assign(acc, {\n [key]: values.map(({ value }) => value).join(','),\n }),\n {},\n ),\n // flatten attributes\n ...Object.entries(groupBy(attributeValues, 'attributeKey.name')).reduce(\n (acc, [key, values]) =>\n Object.assign(acc, {\n [key]: values.map(({ name }) => name).join(','),\n }),\n {},\n ),\n }),\n );\n\n // Write out to CSV\n const headers = uniq(data.map((d) => Object.keys(d)).flat());\n await writeCsv(file, data, headers);\n\n logger.info(\n colors.green(`Successfully wrote ${savedRequests.length} requests to file \"${file}\"`),\n );\n\n return savedRequests;\n}\n"],"mappings":"khBAgCA,eAAsB,EAAqC,CACzD,OACA,OACA,aACA,iBAAiB,EAAE,CACnB,cAAc,IACd,eAAe,GAc4B,CAE3C,IAAM,EAAS,EAA4B,EAAc,EAAK,CACxD,EAAS,MAAM,EAAwB,EAAc,EAAM,CAC/D,SACA,aAAc,EACd,UAAW,QAAQ,IAAI,WACxB,CAAC,CAEF,EAAO,KACL,EAAO,QACL,6DAA6D,EAAe,KAAK,IAAI,GACtF,CACF,CAGD,IAAM,EAAc,MAAM,EAAiB,EAAQ,CACjD,QAAS,EACT,SAAU,CAAC,EAAc,UAAU,CACpC,CAAC,CAEF,MAAM,EAAsB,EAAQ,CAAE,SAAQ,CAAC,CAG/C,IAAM,EAAiD,EAAE,CAGzD,MAAM,EACJ,EACA,KAAO,IAAY,CAEjB,IAAM,EAAmB,MAAM,EAAyB,EAAQ,CAC9D,SAAU,CAAE,UAAW,EAAQ,GAAI,CACnC,SACD,CAAC,CAQF,GAL4B,EAAiB,QAC1C,CAAE,YAAa,IAAW,kBAIN,CAAE,CACvB,IAAM,EAAqB,MAAM,EAA2B,EAAQ,EAAQ,CAC1E,SAAU,CAAE,UAAW,EAAQ,GAAI,CACnC,gBAAiB,GACjB,SACD,CAAC,CACF,EAAc,KAAK,CACjB,GAAG,EACH,qBACA,mBACD,CAAC,GAGN,CACE,cACD,CACF,CAED,IAAM,EAAO,EAAc,KACxB,CACC,kBACA,qBACA,mBACA,GAAG,MACE,CACL,GAAG,EAEH,GAAG,OAAO,QAAQ,EAAQ,EAAoB,OAAO,CAAC,CAAC,QACpD,EAAK,CAAC,EAAK,KACV,OAAO,OAAO,EAAK,EAChB,GAAM,EAAO,KAAK,CAAE,WAAY,EAAM,CAAC,KAAK,IAAI,CAClD,CAAC,CACJ,EAAE,CACH,CAED,GAAG,OAAO,QAAQ,EAAQ,EAAiB,oBAAoB,CAAC,CAAC,QAC9D,EAAK,CAAC,EAAK,KACV,OAAO,OAAO,EAAK,EAChB,GAAM,EAAO,KAAK,CAAE,UAAW,EAAK,CAAC,KAAK,IAAI,CAChD,CAAC,CACJ,EAAE,CACH,CACF,EACF,CAUD,OANA,MAAM,EAAS,EAAM,EADL,EAAK,EAAK,IAAK,GAAM,OAAO,KAAK,EAAE,CAAC,CAAC,MAAM,CACzB,CAAC,CAEnC,EAAO,KACL,EAAO,MAAM,sBAAsB,EAAc,OAAO,qBAAqB,EAAK,GAAG,CACtF,CAEM"}
1
+ {"version":3,"file":"pullManualEnrichmentIdentifiersToCsv-CiEFIvl_.mjs","names":[],"sources":["../src/lib/manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts"],"sourcesContent":["import { RequestAction, RequestStatus } from '@transcend-io/privacy-types';\nimport {\n buildTranscendGraphQLClient,\n createSombraGotInstance,\n fetchAllRequestEnrichers,\n fetchAllRequestIdentifiers,\n validateSombraVersion,\n type RequestEnricher,\n type RequestIdentifier,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport colors from 'colors';\nimport { groupBy, uniq } from 'lodash-es';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { type PrivacyRequest, fetchAllRequests } from '../graphql/index.js';\nimport { writeCsv } from '../helpers/writeCsv.js';\n\nexport interface PrivacyRequestWithIdentifiers extends PrivacyRequest {\n /** Request Enrichers */\n requestEnrichers: RequestEnricher[];\n /** Request Identifiers */\n requestIdentifiers: RequestIdentifier[];\n}\n\n/**\n * Pull the set of manual enrichment jobs to CSV\n *\n * @param options - Options\n * @returns List of requests with identifiers\n */\nexport async function pullManualEnrichmentIdentifiersToCsv({\n file,\n auth,\n sombraAuth,\n requestActions = [],\n concurrency = 100,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** CSV file path */\n file: string;\n /** Transcend API key authentication */\n auth: string;\n /** Sombra API key */\n sombraAuth?: string;\n /** Concurrency */\n concurrency?: number;\n /** The request actions to fetch */\n requestActions?: RequestAction[];\n /** API URL for Transcend backend */\n transcendUrl?: string;\n}): Promise<PrivacyRequestWithIdentifiers[]> {\n // Find all requests made before createdAt that are in a removing data state\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n const sombra = await createSombraGotInstance(transcendUrl, auth, {\n logger,\n sombraApiKey: sombraAuth,\n sombraUrl: process.env.SOMBRA_URL,\n });\n\n logger.info(\n colors.magenta(\n `Pulling manual enrichment requests, filtered for actions: ${requestActions.join(',')}`,\n ),\n );\n\n // Pull all privacy requests\n const allRequests = await fetchAllRequests(client, {\n actions: requestActions,\n statuses: [RequestStatus.Enriching],\n });\n\n await validateSombraVersion(client, { logger });\n\n // Requests to save\n const savedRequests: PrivacyRequestWithIdentifiers[] = [];\n\n // Filter down requests to what is needed\n await map(\n allRequests,\n async (request) => {\n // Fetch enrichers\n const requestEnrichers = await fetchAllRequestEnrichers(client, {\n filterBy: { requestId: request.id },\n logger,\n });\n\n // Check if manual enrichment exists for that request\n const hasManualEnrichment = requestEnrichers.filter(\n ({ status }) => status === 'ACTION_REQUIRED',\n );\n\n // Save request to queue\n if (hasManualEnrichment) {\n const requestIdentifiers = await fetchAllRequestIdentifiers(client, sombra, {\n filterBy: { requestId: request.id },\n skipSombraCheck: true,\n logger,\n });\n savedRequests.push({\n ...request,\n requestIdentifiers,\n requestEnrichers,\n });\n }\n },\n {\n concurrency,\n },\n );\n\n const data = savedRequests.map(\n ({\n attributeValues,\n requestIdentifiers,\n requestEnrichers, // eslint-disable-line @typescript-eslint/no-unused-vars\n ...request\n }) => ({\n ...request,\n // flatten identifiers\n ...Object.entries(groupBy(requestIdentifiers, 'name')).reduce(\n (acc, [key, values]) =>\n Object.assign(acc, {\n [key]: values.map(({ value }) => value).join(','),\n }),\n {},\n ),\n // flatten attributes\n ...Object.entries(groupBy(attributeValues, 'attributeKey.name')).reduce(\n (acc, [key, values]) =>\n Object.assign(acc, {\n [key]: values.map(({ name }) => name).join(','),\n }),\n {},\n ),\n }),\n );\n\n // Write out to CSV\n const headers = uniq(data.map((d) => Object.keys(d)).flat());\n await writeCsv(file, data, headers);\n\n logger.info(\n colors.green(`Successfully wrote ${savedRequests.length} requests to file \"${file}\"`),\n );\n\n return savedRequests;\n}\n"],"mappings":"khBAgCA,eAAsB,EAAqC,CACzD,OACA,OACA,aACA,iBAAiB,EAAE,CACnB,cAAc,IACd,eAAe,GAc4B,CAE3C,IAAM,EAAS,EAA4B,EAAc,EAAK,CACxD,EAAS,MAAM,EAAwB,EAAc,EAAM,CAC/D,SACA,aAAc,EACd,UAAW,QAAQ,IAAI,WACxB,CAAC,CAEF,EAAO,KACL,EAAO,QACL,6DAA6D,EAAe,KAAK,IAAI,GACtF,CACF,CAGD,IAAM,EAAc,MAAM,EAAiB,EAAQ,CACjD,QAAS,EACT,SAAU,CAAC,EAAc,UAAU,CACpC,CAAC,CAEF,MAAM,EAAsB,EAAQ,CAAE,SAAQ,CAAC,CAG/C,IAAM,EAAiD,EAAE,CAGzD,MAAM,EACJ,EACA,KAAO,IAAY,CAEjB,IAAM,EAAmB,MAAM,EAAyB,EAAQ,CAC9D,SAAU,CAAE,UAAW,EAAQ,GAAI,CACnC,SACD,CAAC,CAQF,GAL4B,EAAiB,QAC1C,CAAE,YAAa,IAAW,kBAIN,CAAE,CACvB,IAAM,EAAqB,MAAM,EAA2B,EAAQ,EAAQ,CAC1E,SAAU,CAAE,UAAW,EAAQ,GAAI,CACnC,gBAAiB,GACjB,SACD,CAAC,CACF,EAAc,KAAK,CACjB,GAAG,EACH,qBACA,mBACD,CAAC,GAGN,CACE,cACD,CACF,CAED,IAAM,EAAO,EAAc,KACxB,CACC,kBACA,qBACA,mBACA,GAAG,MACE,CACL,GAAG,EAEH,GAAG,OAAO,QAAQ,EAAQ,EAAoB,OAAO,CAAC,CAAC,QACpD,EAAK,CAAC,EAAK,KACV,OAAO,OAAO,EAAK,EAChB,GAAM,EAAO,KAAK,CAAE,WAAY,EAAM,CAAC,KAAK,IAAI,CAClD,CAAC,CACJ,EAAE,CACH,CAED,GAAG,OAAO,QAAQ,EAAQ,EAAiB,oBAAoB,CAAC,CAAC,QAC9D,EAAK,CAAC,EAAK,KACV,OAAO,OAAO,EAAK,EAChB,GAAM,EAAO,KAAK,CAAE,UAAW,EAAK,CAAC,KAAK,IAAI,CAChD,CAAC,CACJ,EAAE,CACH,CACF,EACF,CAUD,OANA,MAAM,EAAS,EAAM,EADL,EAAK,EAAK,IAAK,GAAM,OAAO,KAAK,EAAE,CAAC,CAAC,MAAM,CACzB,CAAC,CAEnC,EAAO,KACL,EAAO,MAAM,sBAAsB,EAAc,OAAO,qBAAqB,EAAK,GAAG,CACtF,CAEM"}