@topogram/cli 0.3.63 → 0.3.65

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 (344) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +703 -0
  3. package/src/adoption/plan.d.ts +6 -0
  4. package/src/adoption/plan.js +12 -703
  5. package/src/adoption/reporting.d.ts +10 -0
  6. package/src/adoption/review-groups.d.ts +6 -0
  7. package/src/agent-brief.d.ts +3 -0
  8. package/src/agent-brief.js +495 -0
  9. package/src/agent-ops/query-builders/auth.js +375 -0
  10. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  11. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  12. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  13. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  14. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  15. package/src/agent-ops/query-builders/change-risk.js +25 -0
  16. package/src/agent-ops/query-builders/common.js +149 -0
  17. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  18. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  19. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  20. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  21. package/src/agent-ops/query-builders/work-packets.js +417 -0
  22. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  23. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  24. package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
  25. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  26. package/src/agent-ops/query-builders.d.ts +26 -0
  27. package/src/agent-ops/query-builders.js +42 -5021
  28. package/src/archive/archive.d.ts +2 -0
  29. package/src/archive/compact.d.ts +1 -0
  30. package/src/archive/unarchive.d.ts +1 -0
  31. package/src/catalog/constants.js +10 -0
  32. package/src/catalog/copy.js +60 -0
  33. package/src/catalog/diagnostics.js +15 -0
  34. package/src/catalog/entries.js +42 -0
  35. package/src/catalog/files.js +67 -0
  36. package/src/catalog/provenance.js +122 -0
  37. package/src/catalog/source.js +150 -0
  38. package/src/catalog/validation.js +252 -0
  39. package/src/catalog.d.ts +12 -0
  40. package/src/catalog.js +18 -750
  41. package/src/cli/catalog-alias.d.ts +1 -0
  42. package/src/cli/command-parser.js +38 -0
  43. package/src/cli/command-parsers/core.js +102 -0
  44. package/src/cli/command-parsers/generator.js +39 -0
  45. package/src/cli/command-parsers/import.js +44 -0
  46. package/src/cli/command-parsers/legacy-workflow.js +21 -0
  47. package/src/cli/command-parsers/project.js +47 -0
  48. package/src/cli/command-parsers/sdlc.js +47 -0
  49. package/src/cli/command-parsers/shared.js +51 -0
  50. package/src/cli/command-parsers/template.js +48 -0
  51. package/src/cli/commands/agent.js +47 -0
  52. package/src/cli/commands/catalog/check.js +31 -0
  53. package/src/cli/commands/catalog/copy.js +59 -0
  54. package/src/cli/commands/catalog/doctor.js +248 -0
  55. package/src/cli/commands/catalog/help.js +21 -0
  56. package/src/cli/commands/catalog/list.js +52 -0
  57. package/src/cli/commands/catalog/runner.js +92 -0
  58. package/src/cli/commands/catalog/shared.js +17 -0
  59. package/src/cli/commands/catalog/show.js +134 -0
  60. package/src/cli/commands/catalog.js +32 -0
  61. package/src/cli/commands/check.js +268 -0
  62. package/src/cli/commands/doctor.js +268 -0
  63. package/src/cli/commands/emit.js +149 -0
  64. package/src/cli/commands/generate.js +96 -0
  65. package/src/cli/commands/generator-policy/package-info.js +162 -0
  66. package/src/cli/commands/generator-policy/payloads.js +372 -0
  67. package/src/cli/commands/generator-policy/printers.js +159 -0
  68. package/src/cli/commands/generator-policy/runner.js +81 -0
  69. package/src/cli/commands/generator-policy/shared.js +39 -0
  70. package/src/cli/commands/generator-policy.js +17 -0
  71. package/src/cli/commands/generator.js +443 -0
  72. package/src/cli/commands/import/adopt.js +170 -0
  73. package/src/cli/commands/import/check.js +91 -0
  74. package/src/cli/commands/import/diff.js +84 -0
  75. package/src/cli/commands/import/help.js +47 -0
  76. package/src/cli/commands/import/paths.js +277 -0
  77. package/src/cli/commands/import/plan.js +284 -0
  78. package/src/cli/commands/import/refresh.js +470 -0
  79. package/src/cli/commands/import/status-history.js +196 -0
  80. package/src/cli/commands/import/workspace.js +230 -0
  81. package/src/cli/commands/import-runner.js +157 -0
  82. package/src/cli/commands/import.js +35 -0
  83. package/src/cli/commands/inspect.js +55 -0
  84. package/src/cli/commands/new.js +94 -0
  85. package/src/cli/commands/package/constants.js +17 -0
  86. package/src/cli/commands/package/doctor.js +240 -0
  87. package/src/cli/commands/package/help.js +27 -0
  88. package/src/cli/commands/package/lockfile.js +135 -0
  89. package/src/cli/commands/package/npm.js +97 -0
  90. package/src/cli/commands/package/reporting.js +35 -0
  91. package/src/cli/commands/package/runner.js +33 -0
  92. package/src/cli/commands/package/shared.js +9 -0
  93. package/src/cli/commands/package/update-cli.js +252 -0
  94. package/src/cli/commands/package/versions.js +35 -0
  95. package/src/cli/commands/package.js +31 -0
  96. package/src/cli/commands/query/change-plan.js +68 -0
  97. package/src/cli/commands/query/definitions.js +202 -0
  98. package/src/cli/commands/query/import-adopt.js +121 -0
  99. package/src/cli/commands/query/runner/artifacts.js +102 -0
  100. package/src/cli/commands/query/runner/boundaries.js +211 -0
  101. package/src/cli/commands/query/runner/change.js +182 -0
  102. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  103. package/src/cli/commands/query/runner/index.js +31 -0
  104. package/src/cli/commands/query/runner/output.js +12 -0
  105. package/src/cli/commands/query/runner/workflow.js +241 -0
  106. package/src/cli/commands/query/runner.js +3 -0
  107. package/src/cli/commands/query/workflow-context.js +5 -0
  108. package/src/cli/commands/query/workspace.js +274 -0
  109. package/src/cli/commands/query.js +11 -0
  110. package/src/cli/commands/release-rollout.js +257 -0
  111. package/src/cli/commands/release-shared.js +528 -0
  112. package/src/cli/commands/release-status.js +429 -0
  113. package/src/cli/commands/release.js +107 -0
  114. package/src/cli/commands/sdlc.js +168 -0
  115. package/src/cli/commands/setup.js +76 -0
  116. package/src/cli/commands/source.js +291 -0
  117. package/src/cli/commands/template/baseline.js +100 -0
  118. package/src/cli/commands/template/check.js +466 -0
  119. package/src/cli/commands/template/constants.js +8 -0
  120. package/src/cli/commands/template/diagnostics.js +26 -0
  121. package/src/cli/commands/template/help.js +28 -0
  122. package/src/cli/commands/template/lifecycle.js +404 -0
  123. package/src/cli/commands/template/list-show.js +287 -0
  124. package/src/cli/commands/template/policy.js +422 -0
  125. package/src/cli/commands/template/shared.js +127 -0
  126. package/src/cli/commands/template/updates.js +352 -0
  127. package/src/cli/commands/template-runner.js +198 -0
  128. package/src/cli/commands/template.js +43 -0
  129. package/src/cli/commands/trust.js +219 -0
  130. package/src/cli/commands/version.js +40 -0
  131. package/src/cli/commands/widget.js +168 -0
  132. package/src/cli/commands/workflow.js +63 -0
  133. package/src/cli/dispatcher.js +392 -0
  134. package/src/cli/help-dispatch.js +188 -0
  135. package/src/cli/help.js +296 -0
  136. package/src/cli/migration-guidance.js +59 -0
  137. package/src/cli/options.js +96 -0
  138. package/src/cli/output-safety.js +107 -0
  139. package/src/cli/path-normalization.js +29 -0
  140. package/src/cli.js +47 -11711
  141. package/src/example-implementation.d.ts +2 -0
  142. package/src/format.d.ts +1 -0
  143. package/src/generator/api/contracts.js +497 -0
  144. package/src/generator/api/metadata.js +221 -0
  145. package/src/generator/api/openapi.js +559 -0
  146. package/src/generator/api/schema.js +124 -0
  147. package/src/generator/api/types.d.ts +98 -0
  148. package/src/generator/api.js +3 -1195
  149. package/src/generator/check.d.ts +1 -0
  150. package/src/generator/context/bundle.d.ts +1 -0
  151. package/src/generator/context/shared/domain-sdlc.js +282 -0
  152. package/src/generator/context/shared/maintained-boundary.js +665 -0
  153. package/src/generator/context/shared/metrics.js +85 -0
  154. package/src/generator/context/shared/primitives.js +64 -0
  155. package/src/generator/context/shared/relationships.js +453 -0
  156. package/src/generator/context/shared/summaries.js +263 -0
  157. package/src/generator/context/shared/types.d.ts +207 -0
  158. package/src/generator/context/shared.d.ts +44 -0
  159. package/src/generator/context/shared.js +80 -1390
  160. package/src/generator/context/slice/core.js +397 -0
  161. package/src/generator/context/slice/sdlc.js +417 -0
  162. package/src/generator/context/slice/ui-packets.js +183 -0
  163. package/src/generator/context/slice.js +2 -859
  164. package/src/generator/native/parity-bundle.js +2 -1
  165. package/src/generator/registry/index.js +507 -0
  166. package/src/generator/registry.js +18 -504
  167. package/src/generator/runtime/environment/index.js +666 -0
  168. package/src/generator/runtime/environment.js +4 -666
  169. package/src/generator/runtime/runtime-check/index.js +554 -0
  170. package/src/generator/runtime/runtime-check.js +4 -554
  171. package/src/generator/runtime/shared/index.js +572 -0
  172. package/src/generator/runtime/shared.js +19 -570
  173. package/src/generator/shared.d.ts +2 -0
  174. package/src/generator/surfaces/shared.d.ts +3 -0
  175. package/src/generator/surfaces/web/html-escape.js +22 -0
  176. package/src/generator/surfaces/web/react.js +10 -8
  177. package/src/generator/surfaces/web/sveltekit.js +7 -5
  178. package/src/generator/surfaces/web/vanilla.js +8 -4
  179. package/src/generator/widget-conformance/behavior-report.js +258 -0
  180. package/src/generator/widget-conformance/checks.js +371 -0
  181. package/src/generator/widget-conformance/projection-context.js +200 -0
  182. package/src/generator/widget-conformance/report.js +166 -0
  183. package/src/generator/widget-conformance/types.d.ts +121 -0
  184. package/src/generator/widget-conformance.js +3 -824
  185. package/src/generator.d.ts +2 -0
  186. package/src/github-client.js +520 -0
  187. package/src/import/core/context.d.ts +3 -0
  188. package/src/import/core/contracts.d.ts +1 -0
  189. package/src/import/core/registry.d.ts +4 -0
  190. package/src/import/core/runner/candidates.js +217 -0
  191. package/src/import/core/runner/options.js +22 -0
  192. package/src/import/core/runner/reports.js +50 -0
  193. package/src/import/core/runner/run.js +79 -0
  194. package/src/import/core/runner/tracks.js +150 -0
  195. package/src/import/core/runner/ui-drafts.js +337 -0
  196. package/src/import/core/runner.js +3 -698
  197. package/src/import/core/shared/api-routes.js +221 -0
  198. package/src/import/core/shared/candidates.js +97 -0
  199. package/src/import/core/shared/files.js +177 -0
  200. package/src/import/core/shared/next-app.js +389 -0
  201. package/src/import/core/shared/types.d.ts +51 -0
  202. package/src/import/core/shared/ui-routes.js +230 -0
  203. package/src/import/core/shared.js +67 -910
  204. package/src/import/extractors/api/flutter-dio.js +4 -8
  205. package/src/import/extractors/api/react-native-repository.js +4 -8
  206. package/src/import/index.d.ts +4 -0
  207. package/src/import/provenance.d.ts +4 -0
  208. package/src/new-project/constants.js +128 -0
  209. package/src/new-project/create.js +83 -0
  210. package/src/new-project/json.js +28 -0
  211. package/src/new-project/metadata.js +96 -0
  212. package/src/new-project/package-spec.js +161 -0
  213. package/src/new-project/project-files.js +348 -0
  214. package/src/new-project/template-policy.js +269 -0
  215. package/src/new-project/template-resolution.js +368 -0
  216. package/src/new-project/template-snapshots.js +430 -0
  217. package/src/new-project/template-updates.js +512 -0
  218. package/src/new-project/types.d.ts +83 -0
  219. package/src/new-project.js +6 -2188
  220. package/src/npm-safety.js +79 -0
  221. package/src/parser.d.ts +87 -0
  222. package/src/parser.js +118 -0
  223. package/src/path-helpers.d.ts +1 -0
  224. package/src/path-helpers.js +20 -0
  225. package/src/policy/review-boundaries.d.ts +15 -0
  226. package/src/project-config/index.js +564 -0
  227. package/src/project-config.js +19 -560
  228. package/src/reconcile/docs.d.ts +8 -0
  229. package/src/reconcile/journeys.d.ts +1 -0
  230. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  231. package/src/resolver/enrich/bug.js +2 -0
  232. package/src/resolver/enrich/pitch.js +2 -0
  233. package/src/resolver/enrich/requirement.js +2 -0
  234. package/src/resolver/enrich/task.js +2 -0
  235. package/src/resolver/index.js +19 -2089
  236. package/src/resolver/normalize.js +384 -1
  237. package/src/resolver/plans.js +168 -0
  238. package/src/resolver/projections-api.js +494 -0
  239. package/src/resolver/projections-db.js +133 -0
  240. package/src/resolver/projections-ui.js +317 -0
  241. package/src/resolver/shapes.js +251 -0
  242. package/src/resolver/shared.js +278 -0
  243. package/src/resolver/widgets.js +132 -0
  244. package/src/resolver.d.ts +1 -0
  245. package/src/runtime-support.js +29 -0
  246. package/src/sdlc/adopt.d.ts +1 -0
  247. package/src/sdlc/check.d.ts +1 -0
  248. package/src/sdlc/explain.d.ts +1 -0
  249. package/src/sdlc/release.d.ts +1 -0
  250. package/src/sdlc/scaffold.d.ts +1 -0
  251. package/src/sdlc/transition.d.ts +1 -0
  252. package/src/template-trust/constants.js +62 -0
  253. package/src/template-trust/content.js +258 -0
  254. package/src/template-trust/diff.js +92 -0
  255. package/src/template-trust/policy.js +61 -0
  256. package/src/template-trust/record.js +90 -0
  257. package/src/template-trust/status.js +182 -0
  258. package/src/template-trust.js +24 -687
  259. package/src/text-helpers.d.ts +7 -0
  260. package/src/text-helpers.js +245 -0
  261. package/src/topogram-config.js +306 -0
  262. package/src/topogram-types.d.ts +69 -0
  263. package/src/validator/common.js +488 -0
  264. package/src/validator/data-model.js +237 -0
  265. package/src/validator/docs.js +167 -0
  266. package/src/validator/expressions.js +146 -1
  267. package/src/validator/index.d.ts +23 -0
  268. package/src/validator/index.js +32 -3585
  269. package/src/validator/kinds.d.ts +41 -0
  270. package/src/validator/kinds.js +2 -0
  271. package/src/validator/model-helpers.js +46 -0
  272. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  273. package/src/validator/per-kind/bug.js +6 -0
  274. package/src/validator/per-kind/domain.js +15 -2
  275. package/src/validator/per-kind/pitch.js +7 -0
  276. package/src/validator/per-kind/requirement.js +5 -0
  277. package/src/validator/per-kind/task.js +7 -0
  278. package/src/validator/per-kind/widget.js +14 -0
  279. package/src/validator/projections/api-http-async.js +410 -0
  280. package/src/validator/projections/api-http-authz.js +88 -0
  281. package/src/validator/projections/api-http-core.js +205 -0
  282. package/src/validator/projections/api-http-policies.js +339 -0
  283. package/src/validator/projections/api-http-responses.js +233 -0
  284. package/src/validator/projections/api-http.js +44 -0
  285. package/src/validator/projections/db.js +353 -0
  286. package/src/validator/projections/generator-defaults.js +45 -0
  287. package/src/validator/projections/helpers.js +87 -0
  288. package/src/validator/projections/ui-helpers.js +214 -0
  289. package/src/validator/projections/ui-navigation.js +344 -0
  290. package/src/validator/projections/ui-structure.js +364 -0
  291. package/src/validator/projections/ui-widgets.js +493 -0
  292. package/src/validator/projections/ui.js +46 -0
  293. package/src/validator/registry.js +48 -1
  294. package/src/validator/utils.d.ts +20 -0
  295. package/src/validator/utils.js +115 -12
  296. package/src/validator.d.ts +2 -0
  297. package/src/widget-behavior.d.ts +1 -0
  298. package/src/workflows/adoption/index.js +26 -0
  299. package/src/workflows/docs-generate.js +262 -0
  300. package/src/workflows/docs-scan.js +703 -0
  301. package/src/workflows/docs.js +15 -0
  302. package/src/workflows/import-app/api/collect.js +221 -0
  303. package/src/workflows/import-app/api/openapi.js +257 -0
  304. package/src/workflows/import-app/api/routes.js +327 -0
  305. package/src/workflows/import-app/api/sources.js +22 -0
  306. package/src/workflows/import-app/api.js +4 -0
  307. package/src/workflows/import-app/db.js +538 -0
  308. package/src/workflows/import-app/index.js +30 -0
  309. package/src/workflows/import-app/shared.js +218 -0
  310. package/src/workflows/import-app/ui.js +443 -0
  311. package/src/workflows/import-app/workflow.js +159 -0
  312. package/src/workflows/reconcile/adoption-plan/build.js +208 -0
  313. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  314. package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
  315. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  316. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  317. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  318. package/src/workflows/reconcile/adoption-plan.js +32 -0
  319. package/src/workflows/reconcile/auth/closures.js +115 -0
  320. package/src/workflows/reconcile/auth/formatters.js +142 -0
  321. package/src/workflows/reconcile/auth/inference.js +330 -0
  322. package/src/workflows/reconcile/auth/roles.js +122 -0
  323. package/src/workflows/reconcile/auth.js +37 -0
  324. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  325. package/src/workflows/reconcile/bundle-core.js +14 -0
  326. package/src/workflows/reconcile/bundle-shared.js +75 -0
  327. package/src/workflows/reconcile/candidate-model.js +477 -0
  328. package/src/workflows/reconcile/canonical-surface.js +264 -0
  329. package/src/workflows/reconcile/gap-report.js +333 -0
  330. package/src/workflows/reconcile/ids.js +6 -0
  331. package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
  332. package/src/workflows/reconcile/impacts/indexes.js +101 -0
  333. package/src/workflows/reconcile/impacts/patches.js +252 -0
  334. package/src/workflows/reconcile/impacts/reports.js +80 -0
  335. package/src/workflows/reconcile/impacts.js +16 -0
  336. package/src/workflows/reconcile/index.js +7 -0
  337. package/src/workflows/reconcile/renderers.js +461 -0
  338. package/src/workflows/reconcile/summary.js +90 -0
  339. package/src/workflows/reconcile/workflow.js +309 -0
  340. package/src/workflows/shared.js +189 -0
  341. package/src/workflows/types.d.ts +93 -0
  342. package/src/workflows.d.ts +1 -0
  343. package/src/workflows.js +10 -7652
  344. package/src/workspace-docs.d.ts +29 -0
@@ -0,0 +1,330 @@
1
+ // @ts-check
2
+ import { confidenceRank } from "../../docs.js";
3
+ import { inferCapabilityEntityId, normalizeOpenApiPath } from "../../import-app/index.js";
4
+ import { idHintify } from "../../../text-helpers.js";
5
+ import {
6
+ buildAuthClaimReviewGuidance,
7
+ buildAuthOwnershipReviewGuidance,
8
+ buildAuthPermissionReviewGuidance,
9
+ collectAuthClaimSignalMatches,
10
+ describeAuthClaimWhyInferred,
11
+ describeAuthOwnershipWhyInferred,
12
+ describeAuthPermissionWhyInferred
13
+ } from "./formatters.js";
14
+
15
+ /** @param {WorkflowRecord} capability @returns {any} */
16
+ export function permissionResourceStemForCapability(capability) {
17
+ const endpointPath = normalizeOpenApiPath(capability?.endpoint?.path || "");
18
+ const pathSegments = endpointPath
19
+ .split("/")
20
+ .filter(Boolean)
21
+ .filter((/** @type {any} */ segment) => segment !== "{}");
22
+ const firstPathSegment = idHintify(pathSegments[0] || "");
23
+ if (firstPathSegment) {
24
+ return firstPathSegment;
25
+ }
26
+ const entityId = String(capability?.entity_id || inferCapabilityEntityId(capability) || "").replace(/^entity_/, "");
27
+ if (!entityId) {
28
+ return "resource";
29
+ }
30
+ return entityId.endsWith("s") ? entityId : `${entityId}s`;
31
+ }
32
+
33
+ /** @param {string} resource @returns {any} */
34
+ export function singularizePermissionResource(resource) {
35
+ return String(resource || "").endsWith("s") ? String(resource).slice(0, -1) : String(resource || "");
36
+ }
37
+
38
+ /** @param {WorkflowRecord} capability @param {string} resourceStem @returns {any} */
39
+ export function inferPermissionActionForCapability(capability, resourceStem) {
40
+ const capabilityId = String(capability?.id_hint || "");
41
+ const capabilityMatch = capabilityId.match(/^cap_([^_]+)_(.+)$/);
42
+ const resourceSingular = singularizePermissionResource(resourceStem);
43
+ const resourcePrefixes = [resourceStem, resourceSingular].filter(Boolean);
44
+ if (!capabilityMatch) {
45
+ const method = String(capability?.endpoint?.method || "").toUpperCase();
46
+ if (method === "GET") return "read";
47
+ if (method === "POST") return "create";
48
+ if (method === "PATCH" || method === "PUT") return "update";
49
+ if (method === "DELETE") return "delete";
50
+ return null;
51
+ }
52
+ const [, verb, remainder] = capabilityMatch;
53
+ if (verb === "get" || verb === "list") {
54
+ return "read";
55
+ }
56
+ let suffix = remainder;
57
+ for (const prefix of resourcePrefixes) {
58
+ if (suffix === prefix) {
59
+ suffix = "";
60
+ break;
61
+ }
62
+ if (suffix.startsWith(`${prefix}_`)) {
63
+ suffix = suffix.slice(prefix.length + 1);
64
+ break;
65
+ }
66
+ }
67
+ if (!suffix) {
68
+ return verb;
69
+ }
70
+ if (verb === "request") {
71
+ return `request_${suffix}`;
72
+ }
73
+ return ["create", "update", "delete"].includes(verb) ? verb : `${verb}${suffix ? `_${suffix}` : ""}`;
74
+ }
75
+
76
+ /** @param {CandidateBundle} bundle @returns {any} */
77
+ export function inferBundleAuthPermissionHints(bundle) {
78
+ const securedCapabilities = (bundle.capabilities || []).filter((/** @type {any} */ entry) => entry.auth_hint === "secured");
79
+ if (securedCapabilities.length === 0) {
80
+ return [];
81
+ }
82
+
83
+ const docEntries = bundle.docs || [];
84
+ const grouped = new Map();
85
+ for (const capability of securedCapabilities) {
86
+ const resourceStem = permissionResourceStemForCapability(capability);
87
+ const action = inferPermissionActionForCapability(capability, resourceStem);
88
+ if (!resourceStem || !action) {
89
+ continue;
90
+ }
91
+ const permission = `${resourceStem}.${action}`;
92
+ const docPatterns = [
93
+ new RegExp(`\\b${permission.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i"),
94
+ new RegExp(`\\b${resourceStem.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i"),
95
+ new RegExp(`\\b${action.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i")
96
+ ];
97
+ const docMatches = collectAuthClaimSignalMatches(
98
+ docEntries,
99
+ docPatterns,
100
+ (/** @type {any} */ entry) => [entry.id, entry.title, ...(entry.provenance || []), entry.body || ""].filter(Boolean).join(" ")
101
+ );
102
+ const provenanceText = [capability.id_hint, capability.label, capability.endpoint?.path, ...(capability.provenance || [])]
103
+ .filter(Boolean)
104
+ .join(" ");
105
+ const provenanceHits = /\b(permission|policy|scope|authorize|authoriz|allow|guard|access)\b/i.test(provenanceText) ? 1 : 0;
106
+ const existing = grouped.get(permission) || {
107
+ permission,
108
+ confidence: "low",
109
+ review_required: true,
110
+ related_capabilities: [],
111
+ evidence: {
112
+ capability_hits: 0,
113
+ route_hits: 0,
114
+ doc_hits: 0,
115
+ provenance_hits: 0
116
+ },
117
+ explanation: "Secured capability naming and imported route evidence suggest this permission may gate the recovered surface."
118
+ };
119
+ existing.related_capabilities.push(capability.id_hint);
120
+ existing.evidence.capability_hits += 1;
121
+ existing.evidence.route_hits += capability.endpoint?.path ? 1 : 0;
122
+ existing.evidence.doc_hits += docMatches.length;
123
+ existing.evidence.provenance_hits += provenanceHits;
124
+ const confidence = provenanceHits > 0 || docMatches.length > 0 || capability.endpoint?.path ? "medium" : "low";
125
+ if (confidenceRank(confidence) > confidenceRank(existing.confidence)) {
126
+ existing.confidence = confidence;
127
+ }
128
+ grouped.set(permission, existing);
129
+ }
130
+
131
+ return [...grouped.values()]
132
+ .map((/** @type {any} */ entry) => ({
133
+ ...entry,
134
+ related_capabilities: [...new Set(entry.related_capabilities)].sort(),
135
+ why_inferred: describeAuthPermissionWhyInferred(entry),
136
+ review_guidance: buildAuthPermissionReviewGuidance(entry)
137
+ }))
138
+ .sort((/** @type {any} */ a, /** @type {any} */ b) => confidenceRank(b.confidence) - confidenceRank(a.confidence) || a.permission.localeCompare(b.permission));
139
+ }
140
+
141
+ /** @param {CandidateBundle} bundle @returns {any} */
142
+ export function inferBundleAuthClaimHints(bundle) {
143
+ const securedCapabilities = (bundle.capabilities || []).filter((/** @type {any} */ entry) => entry.auth_hint === "secured");
144
+ if (securedCapabilities.length === 0) {
145
+ return [];
146
+ }
147
+
148
+ const candidates = [
149
+ {
150
+ claim: "reviewer",
151
+ claim_value: "true",
152
+ confidenceFloor: "medium",
153
+ capabilityPatterns: [/\breviewer\b/i, /\breview\b/i, /\bapprove\b/i, /\breject\b/i, /\brevision\b/i],
154
+ routePatterns: [/\breviewer\b/i, /\breview\b/i, /\bapprove\b/i, /\breject\b/i, /\brevision\b/i],
155
+ participantPatterns: [/\breviewer\b/i],
156
+ docPatterns: [/\breviewer\b/i, /\breview\b/i, /\bapprove\b/i, /\breject\b/i, /\brevision\b/i],
157
+ explanation: "Review-oriented capability, route, or participant evidence suggests a reviewer claim may gate these actions."
158
+ },
159
+ {
160
+ claim: "tenant",
161
+ claim_value: null,
162
+ confidenceFloor: "low",
163
+ capabilityPatterns: [/\btenant\b/i, /\bworkspace\b/i, /\borganization\b/i, /\borg\b/i],
164
+ routePatterns: [/\btenant\b/i, /\bworkspace\b/i, /\borganization\b/i, /\borg\b/i],
165
+ participantPatterns: [],
166
+ docPatterns: [/\btenant\b/i, /\bworkspace\b/i, /\borganization\b/i, /\borg\b/i],
167
+ explanation: "Tenant or workspace naming suggests a request-scoped claim may be part of access control here."
168
+ }
169
+ ];
170
+
171
+ const routeEntries = [...(bundle.uiRoutes || []), ...securedCapabilities];
172
+ const participantEntries = [...(bundle.actors || []), ...(bundle.roles || [])];
173
+ const docEntries = bundle.docs || [];
174
+
175
+ return candidates
176
+ .map((/** @type {any} */ candidate) => {
177
+ const capabilityMatches = collectAuthClaimSignalMatches(
178
+ securedCapabilities,
179
+ candidate.capabilityPatterns,
180
+ (/** @type {any} */ entry) => [entry.id_hint, entry.label, entry.endpoint?.path, ...(entry.provenance || [])].filter(Boolean).join(" ")
181
+ );
182
+ const routeMatches = collectAuthClaimSignalMatches(
183
+ routeEntries,
184
+ candidate.routePatterns,
185
+ (/** @type {any} */ entry) => [entry.path, entry.route_path, entry.id_hint, entry.label, ...(entry.provenance || [])].filter(Boolean).join(" ")
186
+ );
187
+ const participantMatches = collectAuthClaimSignalMatches(
188
+ participantEntries,
189
+ candidate.participantPatterns,
190
+ (/** @type {any} */ entry) => [entry.id_hint, entry.label, ...(entry.provenance || [])].filter(Boolean).join(" ")
191
+ );
192
+ const docMatches = collectAuthClaimSignalMatches(
193
+ docEntries,
194
+ candidate.docPatterns,
195
+ (/** @type {any} */ entry) => [entry.id, entry.title, ...(entry.provenance || []), entry.body || ""].filter(Boolean).join(" ")
196
+ );
197
+ const signalCount = [
198
+ capabilityMatches.length > 0,
199
+ routeMatches.length > 0,
200
+ participantMatches.length > 0,
201
+ docMatches.length > 0
202
+ ].filter(Boolean).length;
203
+
204
+ if (signalCount === 0) {
205
+ return null;
206
+ }
207
+ if (candidate.claim === "reviewer" && signalCount < 2) {
208
+ return null;
209
+ }
210
+
211
+ const confidence =
212
+ participantMatches.length > 0 || (capabilityMatches.length > 0 && routeMatches.length > 0)
213
+ ? candidate.confidenceFloor
214
+ : "low";
215
+
216
+ return {
217
+ claim: candidate.claim,
218
+ claim_value: candidate.claim_value,
219
+ confidence,
220
+ review_required: true,
221
+ related_capabilities: [...new Set(capabilityMatches.map((/** @type {any} */ entry) => entry.id_hint))].sort(),
222
+ evidence: {
223
+ capability_hits: capabilityMatches.length,
224
+ route_hits: routeMatches.length,
225
+ participant_hits: participantMatches.length,
226
+ doc_hits: docMatches.length
227
+ },
228
+ explanation: candidate.explanation,
229
+ why_inferred: describeAuthClaimWhyInferred({
230
+ claim: candidate.claim,
231
+ claim_value: candidate.claim_value,
232
+ explanation: candidate.explanation,
233
+ evidence: {
234
+ capability_hits: capabilityMatches.length,
235
+ route_hits: routeMatches.length,
236
+ participant_hits: participantMatches.length,
237
+ doc_hits: docMatches.length
238
+ }
239
+ }),
240
+ review_guidance: buildAuthClaimReviewGuidance({
241
+ claim: candidate.claim,
242
+ claim_value: candidate.claim_value
243
+ })
244
+ };
245
+ })
246
+ .filter(Boolean)
247
+ .sort((/** @type {any} */ a, /** @type {any} */ b) => confidenceRank(b.confidence) - confidenceRank(a.confidence) || a.claim.localeCompare(b.claim));
248
+ }
249
+
250
+ /** @param {CandidateBundle} bundle @returns {any} */
251
+ export function inferBundleAuthOwnershipHints(bundle) {
252
+ const securedCapabilities = (bundle.capabilities || []).filter((/** @type {any} */ entry) => entry.auth_hint === "secured");
253
+ if (securedCapabilities.length === 0) {
254
+ return [];
255
+ }
256
+
257
+ const entityFieldEntries = ((bundle.importedFieldEvidence || []).length > 0 ? bundle.importedFieldEvidence : (bundle.entities || []).flatMap((/** @type {any} */ entity) => (entity.fields || []).map((/** @type {any} */ field) => ({
258
+ entity_id: entity.id_hint,
259
+ name: field.name,
260
+ field_type: field.field_type,
261
+ required: field.required
262
+ }))));
263
+ const docEntries = bundle.docs || [];
264
+ const ownershipScopedCapabilities = securedCapabilities.filter((/** @type {any} */ entry) =>
265
+ /^cap_(get|update|close|complete|archive|delete|submit|request|approve|reject)_/.test(entry.id_hint || "")
266
+ );
267
+ if (entityFieldEntries.length === 0 || ownershipScopedCapabilities.length === 0) {
268
+ return [];
269
+ }
270
+
271
+ const candidates = [
272
+ {
273
+ ownership: "owner_or_admin",
274
+ ownership_field: "owner_id",
275
+ confidenceFloor: "medium",
276
+ fieldPatterns: [/^owner_id$/i, /^author_id$/i],
277
+ docPatterns: [/\bowner\b/i, /\bauthor\b/i],
278
+ explanation: "Ownership-style field naming suggests this bundle may authorize detail or lifecycle actions based on resource ownership."
279
+ },
280
+ {
281
+ ownership: "owner_or_admin",
282
+ ownership_field: "assignee_id",
283
+ confidenceFloor: "medium",
284
+ fieldPatterns: [/^assignee_id$/i],
285
+ docPatterns: [/\bassignee\b/i, /\bassigned\b/i],
286
+ explanation: "Assignment-style field naming suggests this bundle may authorize detail or lifecycle actions based on the assigned user."
287
+ }
288
+ ];
289
+
290
+ return candidates
291
+ .map((/** @type {any} */ candidate) => {
292
+ const fieldMatches = entityFieldEntries.filter((/** @type {any} */ entry) => candidate.fieldPatterns.some((/** @type {any} */ pattern) => pattern.test(entry.name || "")));
293
+ const docMatches = collectAuthClaimSignalMatches(
294
+ docEntries,
295
+ candidate.docPatterns,
296
+ (/** @type {any} */ entry) => [entry.id, entry.title, ...(entry.provenance || []), entry.body || ""].filter(Boolean).join(" ")
297
+ );
298
+ if (fieldMatches.length === 0) {
299
+ return null;
300
+ }
301
+ const relatedCapabilities = ownershipScopedCapabilities.map((/** @type {any} */ entry) => entry.id_hint).sort();
302
+ const evidence = {
303
+ field_hits: fieldMatches.length,
304
+ capability_hits: relatedCapabilities.length,
305
+ doc_hits: docMatches.length
306
+ };
307
+ return {
308
+ ownership: candidate.ownership,
309
+ ownership_field: candidate.ownership_field,
310
+ confidence: candidate.confidenceFloor,
311
+ review_required: true,
312
+ related_capabilities: relatedCapabilities,
313
+ related_entities: [...new Set(fieldMatches.map((/** @type {any} */ entry) => entry.entity_id))].sort(),
314
+ evidence,
315
+ explanation: candidate.explanation,
316
+ why_inferred: describeAuthOwnershipWhyInferred({
317
+ ownership: candidate.ownership,
318
+ ownership_field: candidate.ownership_field,
319
+ explanation: candidate.explanation,
320
+ evidence
321
+ }),
322
+ review_guidance: buildAuthOwnershipReviewGuidance({
323
+ ownership: candidate.ownership,
324
+ ownership_field: candidate.ownership_field
325
+ })
326
+ };
327
+ })
328
+ .filter(Boolean)
329
+ .sort((/** @type {any} */ a, /** @type {any} */ b) => confidenceRank(b.confidence) - confidenceRank(a.confidence) || a.ownership_field.localeCompare(b.ownership_field));
330
+ }
@@ -0,0 +1,122 @@
1
+ // @ts-check
2
+ import { confidenceRank } from "../../docs.js";
3
+ import {
4
+ buildAuthRoleReviewGuidance,
5
+ formatAuthRoleFollowupInline
6
+ } from "./formatters.js";
7
+
8
+ /** @param {CandidateBundle} bundle @returns {any} */
9
+ export function inferBundleAuthRoleGuidance(bundle) {
10
+ const roles = bundle.roles || [];
11
+ if (roles.length === 0) {
12
+ return [];
13
+ }
14
+ const authSensitiveCapabilities = new Set([
15
+ ...(bundle.authPermissionHints || []).flatMap((/** @type {any} */ hint) => hint.related_capabilities || []),
16
+ ...(bundle.authClaimHints || []).flatMap((/** @type {any} */ hint) => hint.related_capabilities || []),
17
+ ...(bundle.authOwnershipHints || []).flatMap((/** @type {any} */ hint) => hint.related_capabilities || [])
18
+ ]);
19
+ const claimPreferredRoles = new Set(
20
+ (bundle.authClaimHints || []).flatMap((/** @type {any} */ hint) => {
21
+ if (hint.claim === "reviewer") return ["role_reviewer"];
22
+ if (hint.claim === "tenant") return ["role_admin", "role_manager"];
23
+ return [];
24
+ })
25
+ );
26
+ const ownershipPreferredRoles = new Set(
27
+ (bundle.authOwnershipHints || []).flatMap((/** @type {any} */ hint) => {
28
+ if (hint.ownership_field === "owner_id") return ["role_owner"];
29
+ if (hint.ownership_field === "assignee_id") return ["role_assignee"];
30
+ return [];
31
+ })
32
+ );
33
+
34
+ return roles
35
+ .map((/** @type {any} */ role) => {
36
+ const relatedCapabilities = [...new Set((role.related_capabilities || []).filter((/** @type {any} */ capabilityId) => authSensitiveCapabilities.has(capabilityId)))];
37
+ const directRoleMatch = claimPreferredRoles.has(role.id_hint) || ownershipPreferredRoles.has(role.id_hint);
38
+ if (!directRoleMatch && relatedCapabilities.length === 0) {
39
+ return null;
40
+ }
41
+ /** @type {any[]} */
42
+ const reasonParts = [];
43
+ if (directRoleMatch) {
44
+ reasonParts.push("role naming lines up with inferred auth semantics");
45
+ }
46
+ if (relatedCapabilities.length > 0) {
47
+ reasonParts.push(`${relatedCapabilities.length} related auth-sensitive capability match${relatedCapabilities.length === 1 ? "" : "es"}`);
48
+ }
49
+ return {
50
+ role_id: role.id_hint,
51
+ confidence: role.confidence || "low",
52
+ related_capabilities: relatedCapabilities.sort(),
53
+ related_docs: [...new Set(role.related_docs || [])].sort(),
54
+ why_inferred: `Imported role evidence suggests \`${role.id_hint}\` is likely part of the recovered auth story because ${reasonParts.join(" and ")}.`,
55
+ review_guidance: buildAuthRoleReviewGuidance({ role_id: role.id_hint })
56
+ };
57
+ })
58
+ .filter(Boolean)
59
+ .sort((/** @type {any} */ a, /** @type {any} */ b) =>
60
+ confidenceRank(b.confidence) - confidenceRank(a.confidence) ||
61
+ (b.related_capabilities.length - a.related_capabilities.length) ||
62
+ a.role_id.localeCompare(b.role_id)
63
+ );
64
+ }
65
+
66
+ /** @param {CandidateBundle} bundle @returns {any} */
67
+ export function classifyBundleAuthRoleGuidance(bundle) {
68
+ return (bundle.authRoleGuidance || []).map((/** @type {any} */ entry) => {
69
+ const matchingDocLinks = (bundle.docLinkSuggestions || [])
70
+ .filter((/** @type {any} */ item) => (item.add_related_roles || []).includes(entry.role_id));
71
+ const hasRolePromotion = (bundle.adoptionPlan || [])
72
+ .some((/** @type {any} */ step) => step.action === "promote_role" && step.item === entry.role_id);
73
+ const followupDocIds = matchingDocLinks.map((/** @type {any} */ item) => item.doc_id).sort();
74
+ const followupPatchPaths = matchingDocLinks.map((/** @type {any} */ item) => item.patch_rel_path).filter(Boolean).sort();
75
+ let followupAction = "review_only";
76
+ let followupReason = "Role evidence is still thin enough that this should stay review-only until the participant story is clearer.";
77
+ if (matchingDocLinks.length > 0 && entry.related_capabilities.length === 0) {
78
+ followupAction = "link_role_to_docs";
79
+ followupReason = "Imported docs already exist for this participant signal, and the safer next step is to link the role into those docs before promoting more auth-sensitive changes.";
80
+ } else if (hasRolePromotion && (confidenceRank(entry.confidence) >= confidenceRank("medium") || entry.related_capabilities.length > 0)) {
81
+ followupAction = "promote_role";
82
+ followupReason = "Recovered role evidence is strong enough to promote this role candidate before adopting linked auth-sensitive changes.";
83
+ } else if (matchingDocLinks.length > 0) {
84
+ followupAction = "link_role_to_docs";
85
+ followupReason = "This role already has useful canonical doc anchors, so linking the participant context into docs is the safest next step.";
86
+ }
87
+ const classified = {
88
+ ...entry,
89
+ followup_action: followupAction,
90
+ followup_label: formatAuthRoleFollowupInline({
91
+ ...entry,
92
+ followup_action: followupAction,
93
+ followup_doc_ids: followupDocIds
94
+ }),
95
+ followup_reason: followupReason,
96
+ followup_doc_ids: followupDocIds,
97
+ followup_patch_paths: followupPatchPaths
98
+ };
99
+ return {
100
+ ...classified,
101
+ review_guidance: buildAuthRoleReviewGuidance(classified)
102
+ };
103
+ });
104
+ }
105
+
106
+ /** @param {any[]} docLinkSuggestions @param {any} authRoleGuidance @returns {any} */
107
+ export function annotateDocLinkSuggestionsWithAuthRoleGuidance(docLinkSuggestions, authRoleGuidance) {
108
+ const authRoleMap = new Map((authRoleGuidance || []).map((/** @type {any} */ entry) => [entry.role_id, entry]));
109
+ return (docLinkSuggestions || []).map((/** @type {any} */ item) => {
110
+ const authRoleFollowups = [...new Set(item.add_related_roles || [])]
111
+ .map((/** @type {any} */ roleId) => authRoleMap.get(roleId))
112
+ .filter(Boolean)
113
+ .map((/** @type {any} */ entry) => ({
114
+ role_id: entry.role_id,
115
+ followup_action: entry.followup_action,
116
+ followup_label: entry.followup_label
117
+ }));
118
+ return authRoleFollowups.length > 0
119
+ ? { ...item, auth_role_followups: authRoleFollowups }
120
+ : item;
121
+ });
122
+ }
@@ -0,0 +1,37 @@
1
+ // @ts-check
2
+
3
+ export {
4
+ annotateBundleAuthHintClosures,
5
+ buildAuthHintClosureSummary,
6
+ summarizeHintClosureState
7
+ } from "./auth/closures.js";
8
+ export {
9
+ authClaimPatternMatches,
10
+ buildAuthClaimReviewGuidance,
11
+ buildAuthOwnershipReviewGuidance,
12
+ buildAuthPermissionReviewGuidance,
13
+ buildAuthRoleReviewGuidance,
14
+ collectAuthClaimSignalMatches,
15
+ describeAuthClaimWhyInferred,
16
+ describeAuthOwnershipWhyInferred,
17
+ describeAuthPermissionWhyInferred,
18
+ formatAuthClaimHintInline,
19
+ formatAuthClaimValueInline,
20
+ formatAuthOwnershipHintInline,
21
+ formatAuthPermissionHintInline,
22
+ formatAuthRoleFollowupInline,
23
+ formatAuthRoleGuidanceInline
24
+ } from "./auth/formatters.js";
25
+ export {
26
+ inferBundleAuthClaimHints,
27
+ inferBundleAuthOwnershipHints,
28
+ inferBundleAuthPermissionHints,
29
+ inferPermissionActionForCapability,
30
+ permissionResourceStemForCapability,
31
+ singularizePermissionResource
32
+ } from "./auth/inference.js";
33
+ export {
34
+ annotateDocLinkSuggestionsWithAuthRoleGuidance,
35
+ classifyBundleAuthRoleGuidance,
36
+ inferBundleAuthRoleGuidance
37
+ } from "./auth/roles.js";